aboutsummaryrefslogtreecommitdiff
path: root/Net.Http/src
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-08 14:44:01 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-08 14:44:01 -0500
commitbe6dc557a3b819248b014992eb96c1cb21f8112b (patch)
tree5361530552856ba8154bfcfbfac8377549117c9e /Net.Http/src
parent072a1294646542a73007784d08a35ffcad557b1b (diff)
Initial commit
Diffstat (limited to 'Net.Http/src')
-rw-r--r--Net.Http/src/AlternateProtocolBase.cs96
-rw-r--r--Net.Http/src/ConnectionInfo.cs166
-rw-r--r--Net.Http/src/Core/HttpContext.cs170
-rw-r--r--Net.Http/src/Core/HttpCookie.cs125
-rw-r--r--Net.Http/src/Core/HttpEvent.cs141
-rw-r--r--Net.Http/src/Core/HttpServerBase.cs312
-rw-r--r--Net.Http/src/Core/HttpServerProcessing.cs387
-rw-r--r--Net.Http/src/Core/IConnectionContext.cs62
-rw-r--r--Net.Http/src/Core/IHttpEvent.cs104
-rw-r--r--Net.Http/src/Core/IHttpLifeCycle.cs62
-rw-r--r--Net.Http/src/Core/IHttpResponseBody.cs73
-rw-r--r--Net.Http/src/Core/Request/HttpInputStream.cs222
-rw-r--r--Net.Http/src/Core/Request/HttpRequest.cs284
-rw-r--r--Net.Http/src/Core/Request/HttpRequestBody.cs70
-rw-r--r--Net.Http/src/Core/Request/HttpRequestExtensions.cs304
-rw-r--r--Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs533
-rw-r--r--Net.Http/src/Core/Response/ChunkDataAccumulator.cs228
-rw-r--r--Net.Http/src/Core/Response/ChunkedStream.cs249
-rw-r--r--Net.Http/src/Core/Response/DirectStream.cs96
-rw-r--r--Net.Http/src/Core/Response/HeaderDataAccumulator.cs157
-rw-r--r--Net.Http/src/Core/Response/HttpContextExtensions.cs124
-rw-r--r--Net.Http/src/Core/Response/HttpContextResponseWriting.cs253
-rw-r--r--Net.Http/src/Core/Response/HttpResponse.cs307
-rw-r--r--Net.Http/src/Core/Response/ResponseWriter.cs182
-rw-r--r--Net.Http/src/Core/SharedHeaderReaderBuffer.cs85
-rw-r--r--Net.Http/src/Core/VnHeaderCollection.cs75
-rw-r--r--Net.Http/src/Exceptions/ContentTypeException.cs43
-rw-r--r--Net.Http/src/Exceptions/TerminateConnectionException.cs57
-rw-r--r--Net.Http/src/FileUpload.cs122
-rw-r--r--Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs50
-rw-r--r--Net.Http/src/Helpers/ContentType.cs1180
-rw-r--r--Net.Http/src/Helpers/CoreBufferHelpers.cs188
-rw-r--r--Net.Http/src/Helpers/HelperTypes.cs98
-rw-r--r--Net.Http/src/Helpers/HttpHelpers.cs445
-rw-r--r--Net.Http/src/Helpers/MimeLookups.cs3237
-rw-r--r--Net.Http/src/Helpers/TransportReader.cs114
-rw-r--r--Net.Http/src/Helpers/VnWebHeaderCollection.cs43
-rw-r--r--Net.Http/src/Helpers/WebHeaderExtensions.cs60
-rw-r--r--Net.Http/src/HttpConfig.cs154
-rw-r--r--Net.Http/src/IAlternateProtocol.cs46
-rw-r--r--Net.Http/src/IConnectionInfo.cs150
-rw-r--r--Net.Http/src/IHeaderCollection.cs85
-rw-r--r--Net.Http/src/IMemoryResponseEntity.cs69
-rw-r--r--Net.Http/src/ITransportContext.cs71
-rw-r--r--Net.Http/src/ITransportProvider.cs51
-rw-r--r--Net.Http/src/IWebRoot.cs58
-rw-r--r--Net.Http/src/TransportSecurityInfo.cs121
-rw-r--r--Net.Http/src/VNLib.Net.Http.csproj57
48 files changed, 11366 insertions, 0 deletions
diff --git a/Net.Http/src/AlternateProtocolBase.cs b/Net.Http/src/AlternateProtocolBase.cs
new file mode 100644
index 0000000..929bc33
--- /dev/null
+++ b/Net.Http/src/AlternateProtocolBase.cs
@@ -0,0 +1,96 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: AlternateProtocolBase.cs
+*
+* AlternateProtocolBase.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// A base class for all non-http protocol handlers
+ /// </summary>
+ public abstract class AlternateProtocolBase : MarshalByRefObject, IAlternateProtocol
+ {
+ /// <summary>
+ /// A cancelation source that allows for canceling running tasks, that is linked
+ /// to the server that called <see cref="RunAsync(Stream)"/>.
+ /// </summary>
+ /// <remarks>
+ /// This property is only available while the <see cref="RunAsync(Stream)"/>
+ /// method is executing
+ /// </remarks>
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ protected CancellationTokenSource CancelSource { get; private set; }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///<inheritdoc/>
+ async Task IAlternateProtocol.RunAsync(Stream transport, CancellationToken handlerToken)
+ {
+ //Create new cancel source
+ CancelSource ??= new();
+ //Register the token to cancel the source and save the registration for unregister on dispose
+ CancellationTokenRegistration Registration = handlerToken.Register(CancelSource.Cancel);
+ try
+ {
+ //Call child initialize method
+ await RunAsync(new AlternateProtocolTransportStreamWrapper(transport));
+ CancelSource.Cancel();
+ }
+ finally
+ {
+ //dispose the cancelation registration
+ await Registration.DisposeAsync();
+ //Dispose cancel source
+ CancelSource.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Is the current socket connected using transport security
+ /// </summary>
+ public virtual bool IsSecure { get; init; }
+
+ /// <summary>
+ /// Determines if the instance is pending cancelation
+ /// </summary>
+ public bool IsCancellationRequested => CancelSource.IsCancellationRequested;
+
+ /// <summary>
+ /// Cancels all pending operations. This session will be unusable after this function is called
+ /// </summary>
+ public virtual void CancelAll() => CancelSource?.Cancel();
+
+ /// <summary>
+ /// Called when the protocol swtich handshake has completed and the transport is
+ /// available for the new protocol
+ /// </summary>
+ /// <param name="transport">The transport stream</param>
+ /// <returns>A task that represents the active use of the transport, and when complete all operations are unwound</returns>
+ protected abstract Task RunAsync(Stream transport);
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/ConnectionInfo.cs b/Net.Http/src/ConnectionInfo.cs
new file mode 100644
index 0000000..6e1660d
--- /dev/null
+++ b/Net.Http/src/ConnectionInfo.cs
@@ -0,0 +1,166 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ConnectionInfo.cs
+*
+* ConnectionInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+using VNLib.Net.Http.Core;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http
+{
+ ///<inheritdoc/>
+ internal sealed class ConnectionInfo : IConnectionInfo
+ {
+ private HttpContext Context;
+
+ ///<inheritdoc/>
+ public Uri RequestUri => Context.Request.Location;
+ ///<inheritdoc/>
+ public string Path => RequestUri.LocalPath;
+ ///<inheritdoc/>
+ public string? UserAgent => Context.Request.UserAgent;
+ ///<inheritdoc/>
+ public IHeaderCollection Headers { get; private set; }
+ ///<inheritdoc/>
+ public bool CrossOrigin { get; }
+ ///<inheritdoc/>
+ public bool IsWebSocketRequest { get; }
+ ///<inheritdoc/>
+ public ContentType ContentType => Context.Request.ContentType;
+ ///<inheritdoc/>
+ public HttpMethod Method => Context.Request.Method;
+ ///<inheritdoc/>
+ public HttpVersion ProtocolVersion => Context.Request.HttpVersion;
+ ///<inheritdoc/>
+ public bool IsSecure => Context.Request.EncryptionVersion != SslProtocols.None;
+ ///<inheritdoc/>
+ public SslProtocols SecurityProtocol => Context.Request.EncryptionVersion;
+ ///<inheritdoc/>
+ public Uri? Origin => Context.Request.Origin;
+ ///<inheritdoc/>
+ public Uri? Referer => Context.Request.Referrer;
+ ///<inheritdoc/>
+ public Tuple<long, long>? Range => Context.Request.Range;
+ ///<inheritdoc/>
+ public IPEndPoint LocalEndpoint => Context.Request.LocalEndPoint;
+ ///<inheritdoc/>
+ public IPEndPoint RemoteEndpoint => Context.Request.RemoteEndPoint;
+ ///<inheritdoc/>
+ public Encoding Encoding => Context.ParentServer.Config.HttpEncoding;
+ ///<inheritdoc/>
+ public IReadOnlyDictionary<string, string> RequestCookies => Context.Request.Cookies;
+ ///<inheritdoc/>
+ public IEnumerable<string> Accept => Context.Request.Accept;
+ ///<inheritdoc/>
+ public TransportSecurityInfo? TransportSecurity => Context.GetSecurityInfo();
+
+ ///<inheritdoc/>
+ public bool Accepts(ContentType type)
+ {
+ //Get the content type string from he specified content type
+ string contentType = HttpHelpers.GetContentTypeString(type);
+ return Accepts(contentType);
+ }
+ ///<inheritdoc/>
+ public bool Accepts(string contentType)
+ {
+ if (AcceptsAny())
+ {
+ return true;
+ }
+
+ //If client accepts exact requested encoding
+ if (Accept.Contains(contentType))
+ {
+ return true;
+ }
+
+ //Search accept types to determine if the content type is acceptable
+ bool accepted = Accept
+ .Where(ctype =>
+ {
+ //Get prinary side of mime type
+ ReadOnlySpan<char> primary = contentType.AsSpan().SliceBeforeParam('/');
+ ReadOnlySpan<char> ctSubType = ctype.AsSpan().SliceBeforeParam('/');
+ //See if accepts any subtype, or the primary sub-type matches
+ return ctSubType[0] == '*' || ctSubType.Equals(primary, StringComparison.OrdinalIgnoreCase);
+ }).Any();
+ return accepted;
+ }
+ /// <summary>
+ /// Determines if the connection accepts any content type
+ /// </summary>
+ /// <returns>true if the connection accepts any content typ, false otherwise</returns>
+ private bool AcceptsAny()
+ {
+ //Accept any if no accept header was present, or accept all value */*
+ return Context.Request.Accept.Count == 0 || Accept.Where(static t => t.StartsWith("*/*", StringComparison.OrdinalIgnoreCase)).Any();
+ }
+ ///<inheritdoc/>
+ public void SetCookie(string name, string value, string? domain, string? path, TimeSpan Expires, CookieSameSite sameSite, bool httpOnly, bool secure)
+ {
+ //Create the new cookie
+ HttpCookie cookie = new(name)
+ {
+ Value = value,
+ Domain = domain,
+ Path = path,
+ MaxAge = Expires,
+ //Set the session lifetime flag if the timeout is max value
+ IsSession = Expires == TimeSpan.MaxValue,
+ //If the connection is cross origin, then we need to modify the secure and samsite values
+ SameSite = CrossOrigin ? CookieSameSite.None : sameSite,
+ Secure = secure | CrossOrigin,
+ HttpOnly = httpOnly
+ };
+ //Set the cookie
+ Context.Response.AddCookie(cookie);
+ }
+
+ internal ConnectionInfo(HttpContext ctx)
+ {
+ //Create new header collection
+ Headers = new VnHeaderCollection(ctx);
+ //set co value
+ CrossOrigin = ctx.Request.IsCrossOrigin();
+ //Set websocket status
+ IsWebSocketRequest = ctx.Request.IsWebSocketRequest();
+ //Update the context referrence
+ Context = ctx;
+ }
+
+#nullable disable
+ internal void Clear()
+ {
+ Context = null;
+ (Headers as VnHeaderCollection).Clear();
+ Headers = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/HttpContext.cs b/Net.Http/src/Core/HttpContext.cs
new file mode 100644
index 0000000..43d1975
--- /dev/null
+++ b/Net.Http/src/Core/HttpContext.cs
@@ -0,0 +1,170 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpContext.cs
+*
+* HttpContext.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory.Caching;
+
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed partial class HttpContext : IConnectionContext, IReusable
+ {
+ /// <summary>
+ /// When set as a response flag, disables response compression for
+ /// the current request/response flow
+ /// </summary>
+ public const ulong COMPRESSION_DISABLED_MSK = 0x01UL;
+
+ /// <summary>
+ /// The reusable http request container
+ /// </summary>
+ public readonly HttpRequest Request;
+ /// <summary>
+ /// The reusable response controler
+ /// </summary>
+ public readonly HttpResponse Response;
+ /// <summary>
+ /// The http server that this context is bound to
+ /// </summary>
+ public readonly HttpServer ParentServer;
+ /// <summary>
+ /// The shared transport header reader buffer
+ /// </summary>
+ public readonly SharedHeaderReaderBuffer RequestBuffer;
+
+ /// <summary>
+ /// The response entity body container
+ /// </summary>
+ public readonly IHttpResponseBody ResponseBody;
+
+ /// <summary>
+ /// A collection of flags that can be used to control the way the context
+ /// responds to client requests
+ /// </summary>
+ public readonly BitField ContextFlags;
+
+ /// <summary>
+ /// Gets or sets the alternate application protocol to swtich to
+ /// </summary>
+ public IAlternateProtocol? AlternateProtocol { get; set; }
+
+ private readonly ResponseWriter responseWriter;
+ private ITransportContext? _ctx;
+
+ public HttpContext(HttpServer server)
+ {
+ /*
+ * Local method for retreiving the transport stream,
+ * this adds protection/debug from response/request
+ * containers not allowed to maintain referrences
+ * to a transport stream after it has been released
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ Stream GetStream() => _ctx!.ConnectionStream;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ HttpVersion GetVersion() => Request.HttpVersion;
+
+ ParentServer = server;
+
+ //Create new request
+ Request = new HttpRequest(GetStream);
+
+ //create a new response object
+ Response = new HttpResponse(
+ server.Config.HttpEncoding,
+ ParentServer.Config.ResponseHeaderBufferSize,
+ ParentServer.Config.ChunkedResponseAccumulatorSize,
+ GetStream,
+ GetVersion);
+
+ //The shared request parsing buffer
+ RequestBuffer = new(server.Config.HeaderBufferSize);
+
+ //Init response writer
+ ResponseBody = responseWriter = new ResponseWriter();
+
+ ContextFlags = new(0);
+ }
+
+ public TransportSecurityInfo? GetSecurityInfo() => _ctx?.GetSecurityInfo();
+
+
+ #region LifeCycle Hooks
+
+ ///<inheritdoc/>
+ public void InitializeContext(ITransportContext ctx) => _ctx = ctx;
+
+ ///<inheritdoc/>
+ public void BeginRequest()
+ {
+ //Clear all flags
+ ContextFlags.ClearAll();
+
+ //Lifecycle on new request
+ Request.OnNewRequest();
+ Response.OnNewRequest();
+ RequestBuffer.OnNewRequest();
+
+ //Initialize the request
+ Request.Initialize(_ctx!, ParentServer.Config.DefaultHttpVersion);
+ }
+
+ ///<inheritdoc/>
+ public void EndRequest()
+ {
+ AlternateProtocol = null;
+
+ Request.OnComplete();
+ Response.OnComplete();
+ RequestBuffer.OnComplete();
+ responseWriter.OnComplete();
+ }
+
+ void IReusable.Prepare()
+ {
+ Request.OnPrepare();
+ Response.OnPrepare();
+ RequestBuffer.OnPrepare();
+ }
+
+ bool IReusable.Release()
+ {
+ _ctx = null;
+
+ //Release response/requqests
+ Request.OnRelease();
+ Response.OnRelease();
+ RequestBuffer.OnRelease();
+
+ return true;
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/HttpCookie.cs b/Net.Http/src/Core/HttpCookie.cs
new file mode 100644
index 0000000..f5408b2
--- /dev/null
+++ b/Net.Http/src/Core/HttpCookie.cs
@@ -0,0 +1,125 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpCookie.cs
+*
+* HttpCookie.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+ internal class HttpCookie : IStringSerializeable, IEquatable<HttpCookie>
+ {
+ public string Name { get; }
+ public string? Value { get; init; }
+ public string? Domain { get; init; }
+ public string? Path { get; init; }
+ public TimeSpan MaxAge { get; init; }
+ public CookieSameSite SameSite { get; init; }
+ public bool Secure { get; init; }
+ public bool HttpOnly { get; init; }
+ public bool IsSession { get; init; }
+
+ public HttpCookie(string name)
+ {
+ this.Name = name;
+ }
+
+ public string Compile()
+ {
+ throw new NotImplementedException();
+ }
+ public void Compile(ref ForwardOnlyWriter<char> writer)
+ {
+ //set the name of the cookie
+ writer.Append(Name);
+ writer.Append('=');
+ //set name
+ writer.Append(Value);
+ //Only set the max age parameter if the cookie is not a session cookie
+ if (!IsSession)
+ {
+ writer.Append("; Max-Age=");
+ writer.Append((int)MaxAge.TotalSeconds);
+ }
+ //Make sure domain is set
+ if (!string.IsNullOrWhiteSpace(Domain))
+ {
+ writer.Append("; Domain=");
+ writer.Append(Domain);
+ }
+ //Check and set path
+ if (!string.IsNullOrWhiteSpace(Path))
+ {
+ //Set path
+ writer.Append("; Path=");
+ writer.Append(Path);
+ }
+ writer.Append("; SameSite=");
+ //Set the samesite flag based on the enum value
+ switch (SameSite)
+ {
+ case CookieSameSite.None:
+ writer.Append("None");
+ break;
+ case CookieSameSite.SameSite:
+ writer.Append("Strict");
+ break;
+ case CookieSameSite.Lax:
+ default:
+ writer.Append("Lax");
+ break;
+ }
+ //Set httponly flag
+ if (HttpOnly)
+ {
+ writer.Append("; HttpOnly");
+ }
+ //Set secure flag
+ if (Secure)
+ {
+ writer.Append("; Secure");
+ }
+ }
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+
+ public override int GetHashCode() => Name.GetHashCode();
+
+ public override bool Equals(object? obj)
+ {
+ return obj is HttpCookie other && Equals(other);
+ }
+
+ public bool Equals(HttpCookie? other)
+ {
+ return other != null && Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/HttpEvent.cs b/Net.Http/src/Core/HttpEvent.cs
new file mode 100644
index 0000000..7d7c1e7
--- /dev/null
+++ b/Net.Http/src/Core/HttpEvent.cs
@@ -0,0 +1,141 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpEvent.cs
+*
+* HttpEvent.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+ internal sealed class HttpEvent : MarshalByRefObject, IHttpEvent
+ {
+ private HttpContext Context;
+ private ConnectionInfo _ci;
+
+ internal HttpEvent(HttpContext ctx)
+ {
+ Context = ctx;
+ _ci = new ConnectionInfo(ctx);
+ }
+
+ ///<inheritdoc/>
+ IConnectionInfo IHttpEvent.Server => _ci;
+
+ ///<inheritdoc/>
+ HttpServer IHttpEvent.OriginServer => Context.ParentServer;
+
+ ///<inheritdoc/>
+ IReadOnlyDictionary<string, string> IHttpEvent.QueryArgs => Context.Request.RequestBody.QueryArgs;
+ ///<inheritdoc/>
+ IReadOnlyDictionary<string, string> IHttpEvent.RequestArgs => Context.Request.RequestBody.RequestArgs;
+ ///<inheritdoc/>
+ IReadOnlyList<FileUpload> IHttpEvent.Files => Context.Request.RequestBody.Uploads;
+
+ ///<inheritdoc/>
+ void IHttpEvent.DisableCompression() => Context.ContextFlags.Set(HttpContext.COMPRESSION_DISABLED_MSK);
+
+ ///<inheritdoc/>
+ void IHttpEvent.DangerousChangeProtocol(IAlternateProtocol protocolHandler)
+ {
+ if(Context.AlternateProtocol != null)
+ {
+ throw new InvalidOperationException("A protocol handler was already specified");
+ }
+
+ _ = protocolHandler ?? throw new ArgumentNullException(nameof(protocolHandler));
+
+ //Set 101 status code
+ Context.Respond(HttpStatusCode.SwitchingProtocols);
+ Context.AlternateProtocol = protocolHandler;
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IHttpEvent.CloseResponse(HttpStatusCode code) => Context.Respond(code);
+
+ ///<inheritdoc/>
+ void IHttpEvent.CloseResponse(HttpStatusCode code, ContentType type, Stream stream)
+ {
+ //Check if the stream is valid. We will need to read the stream, and we will also need to get the length property
+ if (!stream.CanSeek || !stream.CanRead)
+ {
+ throw new IOException("The stream.Length property must be available and the stream must be readable");
+ }
+
+ //If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
+ if (stream.Length == 0)
+ {
+ return;
+ }
+
+ //Set status code
+ Context.Response.SetStatusCode(code);
+
+ //Finally store the stream input
+ if(!(Context.ResponseBody as ResponseWriter)!.TrySetResponseBody(stream))
+ {
+ throw new InvalidOperationException("A response body has already been set");
+ }
+
+ //Set content type header after body
+ Context.Response.Headers[HttpResponseHeader.ContentType] = HttpHelpers.GetContentTypeString(type);
+ }
+
+ ///<inheritdoc/>
+ void IHttpEvent.CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity)
+ {
+ //If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
+ if (entity.Remaining == 0)
+ {
+ return;
+ }
+
+ //Set status code
+ Context.Response.SetStatusCode(code);
+
+ //Finally store the stream input
+ if (!(Context.ResponseBody as ResponseWriter)!.TrySetResponseBody(entity))
+ {
+ throw new InvalidOperationException("A response body has already been set");
+ }
+
+ //Set content type header after body
+ Context.Response.Headers[HttpResponseHeader.ContentType] = HttpHelpers.GetContentTypeString(type);
+ }
+
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+ internal void Clear()
+ {
+ //Clean up referrence types and cleanable objects
+ Context = null;
+ _ci.Clear();
+ _ci = null;
+ }
+#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/HttpServerBase.cs b/Net.Http/src/Core/HttpServerBase.cs
new file mode 100644
index 0000000..3a50672
--- /dev/null
+++ b/Net.Http/src/Core/HttpServerBase.cs
@@ -0,0 +1,312 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpServerBase.cs
+*
+* HttpServerBase.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This file is the base of the HTTP server class that provides
+ * consts, statics, fields, and properties of the HttpServer class.
+ *
+ * Processing of HTTP connections and entities is contained in the
+ * processing partial file.
+ *
+ * Processing is configured to be asynchronous, utilizing .NETs
+ * asynchronous compilation services. To facilitate this but continue
+ * to use object caching, reusable stores must be usable across threads
+ * to function safely with async programming practices.
+ */
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+using VNLib.Utils.Logging;
+using VNLib.Utils.Memory.Caching;
+
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+
+ /// <summary>
+ /// Provides a resource efficient, high performance, single library HTTP(s) server,
+ /// with extensable processors and transport providers.
+ /// This class cannot be inherited
+ /// </summary>
+ public sealed partial class HttpServer : ICacheHolder
+ {
+ /// <summary>
+ /// The host key that determines a "wildcard" host, meaning the
+ /// default connection handler when an incomming connection has
+ /// not specific route
+ /// </summary>
+ public const string WILDCARD_KEY = "*";
+
+ private readonly ITransportProvider Transport;
+ private readonly IReadOnlyDictionary<string, IWebRoot> ServerRoots;
+
+ #region caches
+ /// <summary>
+ /// The cached HTTP1/1 keepalive timeout header value
+ /// </summary>
+ private readonly string KeepAliveTimeoutHeaderValue;
+ /// <summary>
+ /// Reusable store for obtaining <see cref="HttpContext"/>
+ /// </summary>
+ private readonly ObjectRental<HttpContext> ContextStore;
+ /// <summary>
+ /// The cached header-line termination value
+ /// </summary>
+ private readonly ReadOnlyMemory<byte> HeaderLineTermination;
+ #endregion
+
+ /// <summary>
+ /// The <see cref="HttpConfig"/> for the current server
+ /// </summary>
+ public HttpConfig Config { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the server is listening for connections
+ /// </summary>
+ public bool Running { get; private set; }
+
+ private CancellationTokenSource? StopToken;
+
+ /// <summary>
+ /// Creates a new <see cref="HttpServer"/> with the specified configration copy (using struct).
+ /// Immutable data structures are initialzed.
+ /// </summary>
+ /// <param name="config">The configuration used to create the instance</param>
+ /// <param name="transport">The transport provider to listen to connections from</param>
+ /// <param name="sites">A collection of <see cref="IWebRoot"/>s that route incomming connetctions</param>
+ /// <exception cref="ArgumentException"></exception>
+ public HttpServer(HttpConfig config, ITransportProvider transport, IEnumerable<IWebRoot> sites)
+ {
+ //Validate the configuration
+ ValidateConfig(in config);
+
+ Config = config;
+ //Configure roots and their directories
+ ServerRoots = sites.ToDictionary(static r => r.Hostname, static tv => tv, StringComparer.OrdinalIgnoreCase);
+ //Compile and store the timeout keepalive header
+ KeepAliveTimeoutHeaderValue = $"timeout={(int)Config.ConnectionKeepAlive.TotalSeconds}";
+ //Store termination for the current instance
+ HeaderLineTermination = config.HttpEncoding.GetBytes(HttpHelpers.CRLF);
+ //Create a new context store
+ ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this));
+ //Setup config copy with the internal http pool
+ Transport = transport;
+ }
+
+ private static void ValidateConfig(in HttpConfig conf)
+ {
+ _ = conf.HttpEncoding ?? throw new ArgumentException("HttpEncoding cannot be null", nameof(conf));
+ _ = conf.ServerLog ?? throw new ArgumentException("ServerLog cannot be null", nameof(conf));
+
+ if (conf.ActiveConnectionRecvTimeout < -1)
+ {
+ throw new ArgumentException("ActiveConnectionRecvTimeout cannot be less than -1", nameof(conf));
+ }
+
+ //Chunked data accumulator must be at least 64 bytes (arbinrary value)
+ if (conf.ChunkedResponseAccumulatorSize < 64 || conf.ChunkedResponseAccumulatorSize == int.MaxValue)
+ {
+ throw new ArgumentException("ChunkedResponseAccumulatorSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.CompressionLimit < 0)
+ {
+ throw new ArgumentException("CompressionLimit cannot be less than 0, set to 0 to disable response compression", nameof(conf));
+ }
+
+ if (conf.ConnectionKeepAlive < TimeSpan.Zero)
+ {
+ throw new ArgumentException("ConnectionKeepAlive cannot be less than 0", nameof(conf));
+ }
+
+ if (conf.DefaultHttpVersion == HttpVersion.NotSupported)
+ {
+ throw new ArgumentException("DefaultHttpVersion cannot be NotSupported", nameof(conf));
+ }
+
+ if (conf.DiscardBufferSize < 64)
+ {
+ throw new ArgumentException("DiscardBufferSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.FormDataBufferSize < 64)
+ {
+ throw new ArgumentException("FormDataBufferSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.HeaderBufferSize < 128)
+ {
+ throw new ArgumentException("HeaderBufferSize cannot be less than 128 bytes", nameof(conf));
+ }
+
+ if (conf.MaxFormDataUploadSize < 0)
+ {
+ throw new ArgumentException("MaxFormDataUploadSize cannot be less than 0, set to 0 to disable form-data uploads", nameof(conf));
+ }
+
+ if (conf.MaxOpenConnections < 0)
+ {
+ throw new ArgumentException("MaxOpenConnections cannot be less than 0", nameof(conf));
+ }
+
+ if (conf.MaxRequestHeaderCount < 1)
+ {
+ throw new ArgumentException("MaxRequestHeaderCount cannot be less than 1", nameof(conf));
+ }
+
+ if (conf.MaxUploadSize < 0)
+ {
+ throw new ArgumentException("MaxUploadSize cannot be less than 0", nameof(conf));
+ }
+
+ if (conf.ResponseBufferSize < 64)
+ {
+ throw new ArgumentException("ResponseBufferSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.ResponseHeaderBufferSize < 128)
+ {
+ throw new ArgumentException("ResponseHeaderBufferSize cannot be less than 128 bytes", nameof(conf));
+ }
+
+ if (conf.SendTimeout < 1)
+ {
+ throw new ArgumentException("SendTimeout cannot be less than 1 millisecond", nameof(conf));
+ }
+ }
+
+ /// <summary>
+ /// Begins listening for connections on configured interfaces for configured hostnames.
+ /// </summary>
+ /// <param name="token">A token used to stop listening for incomming connections and close all open websockets</param>
+ /// <returns>A task that resolves when the server has exited</returns>
+ /// <exception cref="SocketException"></exception>
+ /// <exception cref="ThreadStateException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public Task Start(CancellationToken token)
+ {
+ StopToken = CancellationTokenSource.CreateLinkedTokenSource(token);
+ //Start servers with the new token source
+ Transport.Start(token);
+ //Start the listen task
+ return Task.Run(ListenWorkerDoWork, token);
+ }
+
+ /*
+ * An SslStream may throw a win32 exception with HRESULT 0x80090327
+ * when processing a client certificate (I believe anyway) only
+ * an issue on some clients (browsers)
+ */
+
+ private const int UKNOWN_CERT_AUTH_HRESULT = unchecked((int)0x80090327);
+
+ /// <summary>
+ /// An invlaid frame size may happen if data is recieved on an open socket
+ /// but does not contain valid SSL handshake data
+ /// </summary>
+ private const int INVALID_FRAME_HRESULT = unchecked((int)0x80131620);
+
+ /*
+ * A worker task that listens for connections from the transport
+ */
+ private async Task ListenWorkerDoWork()
+ {
+ //Set running flag
+ Running = true;
+
+ Config.ServerLog.Information("HTTP server {hc} listening for connections", GetHashCode());
+ //Listen for connections until canceled
+ while (true)
+ {
+ try
+ {
+ //Listen for new connection
+ ITransportContext ctx = await Transport.AcceptAsync(StopToken!.Token);
+ //Try to dispatch the recieved event
+ _ = DataReceivedAsync(ctx).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ //Closing, exit loop
+ break;
+ }
+ catch (AuthenticationException ae)
+ {
+ Config.ServerLog.Error(ae);
+ }
+ catch (Exception ex)
+ {
+ Config.ServerLog.Error(ex);
+ }
+ }
+ //Clear all caches
+ CacheHardClear();
+ //Clear running flag
+ Running = false;
+ Config.ServerLog.Information("HTTP server {hc} exiting", GetHashCode());
+ }
+
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public void CacheClear() => ContextStore.CacheClear();
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public void CacheHardClear() => ContextStore.CacheHardClear();
+
+ /// <summary>
+ /// Writes the specialized log for a socket exception
+ /// </summary>
+ /// <param name="se">The socket exception to log</param>
+ public void WriteSocketExecption(SocketException se)
+ {
+ //When clause guards nulls
+ switch (se.SocketErrorCode)
+
+ {
+ //Ignore aborted messages
+ case SocketError.ConnectionAborted:
+ return;
+ case SocketError.ConnectionReset:
+ Config.ServerLog.Debug("Connecion reset by client");
+ return;
+ case SocketError.TimedOut:
+ Config.ServerLog.Debug("Socket operation timed out");
+ return;
+ default:
+ Config.ServerLog.Information(se);
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/HttpServerProcessing.cs b/Net.Http/src/Core/HttpServerProcessing.cs
new file mode 100644
index 0000000..881b66c
--- /dev/null
+++ b/Net.Http/src/Core/HttpServerProcessing.cs
@@ -0,0 +1,387 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpServerProcessing.cs
+*
+* HttpServerProcessing.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Logging;
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+ public sealed partial class HttpServer
+ {
+
+ private int OpenConnectionCount;
+
+ //Event handler method for processing incoming data events
+ private async Task DataReceivedAsync(ITransportContext transportContext)
+ {
+ //Increment open connection count
+ Interlocked.Increment(ref OpenConnectionCount);
+
+ //Rent a new context object to reuse
+ HttpContext context = ContextStore.Rent();
+
+ try
+ {
+ //Set write timeout
+ transportContext.ConnectionStream.WriteTimeout = Config.SendTimeout;
+
+ //Init stream
+ context.InitializeContext(transportContext);
+
+ //Keep the transport open and listen for messages as long as keepalive is enabled
+ do
+ {
+ //Set rx timeout low for initial reading
+ transportContext.ConnectionStream.ReadTimeout = Config.ActiveConnectionRecvTimeout;
+
+ //Process the request
+ ERRNO keepalive = await ProcessHttpEventAsync(transportContext, context);
+
+ //If the connection is closed, we can return
+ if (!keepalive)
+ {
+ break;
+ }
+
+ //Set inactive keeaplive timeout
+ transportContext.ConnectionStream.ReadTimeout = (int)Config.ConnectionKeepAlive.TotalMilliseconds;
+
+ //"Peek" or wait for more data to begin another request (may throw timeout exception when timmed out)
+ await transportContext.ConnectionStream.ReadAsync(Memory<byte>.Empty, StopToken!.Token);
+
+ } while (true);
+ }
+ //Catch wrapped socket exceptions
+ catch(IOException ioe) when(ioe.InnerException is SocketException se)
+ {
+ WriteSocketExecption(se);
+ }
+ catch(SocketException se)
+ {
+ WriteSocketExecption(se);
+ }
+ catch (OperationCanceledException oce)
+ {
+ Config.ServerLog.Debug("Failed to receive transport data within a timeout period {m}, connection closed", oce.Message);
+ }
+ catch(Exception ex)
+ {
+ Config.ServerLog.Error(ex);
+ }
+
+ //Dec open connection count
+ Interlocked.Decrement(ref OpenConnectionCount);
+
+ //Return context to store
+ ContextStore.Return(context);
+
+ //Close the transport async
+ try
+ {
+ await transportContext.CloseConnectionAsync();
+ }
+ catch(Exception ex)
+ {
+ Config.ServerLog.Error(ex);
+ }
+ }
+
+
+ /// <summary>
+ /// Main event handler for all incoming connections
+ /// </summary>
+ /// <param name="transportContext">The <see cref="ITransportContext"/> describing the incoming connection</param>
+ /// <param name="context">Reusable context object</param>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ private async Task<ERRNO> ProcessHttpEventAsync(ITransportContext transportContext, HttpContext context)
+ {
+ //Prepare http context to process a new message
+ context.BeginRequest();
+
+ try
+ {
+ //Try to parse the http request (may throw exceptions, let them propagate to the transport layer)
+ int status = (int)ParseRequest(transportContext, context);
+
+ //Check status code for socket error, if so, return false to close the connection
+ if (status >= 1000)
+ {
+ return false;
+ }
+#if DEBUG
+ //Write debug request log
+ if (Config.RequestDebugLog != null)
+ {
+ Config.RequestDebugLog.Verbose(context.Request.ToString());
+ }
+#endif
+ //process the request
+ ERRNO keepalive = await ProcessRequestAsync(context, (HttpStatusCode)status);
+ //Store alternate protocol if set
+ IAlternateProtocol? alternateProtocol = context.AlternateProtocol;
+ //Close the response
+ await context.WriteResponseAsync(StopToken!.Token);
+ //See if an alterate protocol was specified
+ if (alternateProtocol != null)
+ {
+ //Disable transport timeouts
+ transportContext.ConnectionStream.WriteTimeout = Timeout.Infinite;
+ transportContext.ConnectionStream.ReadTimeout = Timeout.Infinite;
+ //Exec the protocol handler and pass the transport stream
+ await alternateProtocol.RunAsync(transportContext.ConnectionStream, StopToken!.Token);
+
+ //Clear keepalive flag to close the connection
+ keepalive = false;
+ }
+
+ return keepalive;
+ }
+ finally
+ {
+ //Clean end request
+ context.EndRequest();
+ }
+ }
+
+ /// <summary>
+ /// Reads data synchronously from the transport and attempts to parse an HTTP message and
+ /// built a request.
+ /// </summary>
+ /// <param name="transport"></param>
+ /// <param name="ctx"></param>
+ /// <returns>0 if the request was successfully parsed, the <see cref="HttpStatusCode"/>
+ /// to return to the client because the entity could not be processed</returns>
+ /// <remarks>
+ /// <para>
+ /// This method is synchronous for multiple memory optimization reasons,
+ /// and performance is not expected to be reduced as the transport layer should
+ /// <br></br>
+ /// only raise an event when a socket has data available to be read, and entity
+ /// header sections are expected to fit within a single TCP buffer.
+ /// </para>
+ /// </remarks>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ private HttpStatusCode ParseRequest(ITransportContext transport, HttpContext ctx)
+ {
+ //Init parser
+ TransportReader reader = new (transport.ConnectionStream, ctx.RequestBuffer, Config.HttpEncoding, HeaderLineTermination);
+
+ try
+ {
+ Span<char> lineBuf = ctx.RequestBuffer.CharBuffer;
+
+ Http11ParseExtensions.Http1ParseState parseState = new();
+
+ //Parse the request line
+ HttpStatusCode code = ctx.Request.Http1ParseRequestLine(ref parseState, ref reader, in lineBuf);
+
+ if (code > 0)
+ {
+ return code;
+ }
+ //Parse the headers
+ code = ctx.Request.Http1ParseHeaders(ref parseState, ref reader, Config, in lineBuf);
+ if (code > 0)
+ {
+ return code;
+ }
+ //Prepare entity body for request
+ code = ctx.Request.Http1PrepareEntityBody(ref parseState, ref reader, Config);
+ if (code > 0)
+ {
+ return code;
+ }
+ //Success!
+ return 0;
+ }
+ //Catch exahusted buffer request
+ catch (OutOfMemoryException)
+ {
+ return HttpStatusCode.RequestHeaderFieldsTooLarge;
+ }
+ catch (UriFormatException)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ private async ValueTask<ERRNO> ProcessRequestAsync(HttpContext context, HttpStatusCode status)
+ {
+ //Check status
+ if (status != 0)
+ {
+ /*
+ * If the status of the parsing was not successfull the transnport is considered
+ * an unknowns state and could still have data which could corrupt communications
+ * or worse, contatin an attack. I am choosing to drop the transport and close the
+ * connection if parsing the request fails
+ */
+ //Close the connection when we exit
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ //Return status code, if the the expect header was set, return expectation failed, otherwise return the result status code
+ context.Respond(context.Request.Expect ? HttpStatusCode.ExpectationFailed : status);
+ //exit and close connection (default result will close the context)
+ return false;
+ }
+ //We only support version 1 and 1/1
+ if ((context.Request.HttpVersion & (HttpVersion.Http11 | HttpVersion.Http1)) == 0)
+ {
+ //Close the connection when we exit
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ context.Respond(HttpStatusCode.HttpVersionNotSupported);
+ return false;
+ }
+ //Check open connection count (not super accurate, or might not be atomic)
+ if (OpenConnectionCount > Config.MaxOpenConnections)
+ {
+ //Close the connection and return 503
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ context.Respond(HttpStatusCode.ServiceUnavailable);
+ return false;
+ }
+
+ //Store keepalive value from request, and check if keepalives are enabled by the configuration
+ bool keepalive = context.Request.KeepAlive & Config.ConnectionKeepAlive > TimeSpan.Zero;
+
+ //Set connection header (only for http1 and 1.1)
+ if (keepalive)
+ {
+ context.Response.Headers[HttpResponseHeader.Connection] = "keep-alive";
+ context.Response.Headers[HttpResponseHeader.KeepAlive] = KeepAliveTimeoutHeaderValue;
+ }
+ else
+ {
+ //Set connection closed
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ }
+ //Get the server root for the specified location
+ if (!ServerRoots.TryGetValue(context.Request.Location.DnsSafeHost, out IWebRoot? root) && !ServerRoots.TryGetValue(WILDCARD_KEY, out root))
+ {
+ context.Respond(HttpStatusCode.NotFound);
+ //make sure control leaves
+ return keepalive;
+ }
+ //check for redirects
+ if (root.Redirects.TryGetValue(context.Request.Location.LocalPath, out Redirect? r))
+ {
+ //301
+ context.Redirect301(r.RedirectUrl);
+ //Return keepalive
+ return keepalive;
+ }
+ //Check the expect header and return an early status code
+ if (context.Request.Expect)
+ {
+ //send a 100 status code
+ await context.Response.SendEarly100ContinueAsync();
+ }
+ /*
+ * Initialze the request body state, which may read/buffer the request
+ * entity body. When doing so, the only exceptions that should be
+ * generated are IO, OutOfMemory, and Overflow. IOE should
+ * be raised to the transport as it will only be thrown if the transport
+ * is in an unusable state.
+ *
+ * OOM and Overflow should only be raised if an over-sized entity
+ * body was allowed to be read in. The Parse method should have guarded
+ * form data size so oom or overflow would be bugs, and we can let
+ * them get thrown
+ */
+ await context.Request.InitRequestBodyAsync(Config.FormDataBufferSize, Config.HttpEncoding);
+ try
+ {
+ await ProcessAsync(root, context);
+ return keepalive;
+ }
+ //The user-code requested termination of the connection
+ catch (TerminateConnectionException tce)
+ {
+ //Log the event as a debug so user can see the result
+ Config.ServerLog.Debug(tce, "User-code requested a connection termination");
+ //See if the exception requested an error code response
+ if (tce.Code > 0)
+ {
+ //close response with status code
+ context.Respond(tce.Code);
+ }
+ else
+ {
+ //Clear any currently set headers since no response is requested
+ context.Response.Headers.Clear();
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Processes a client connection after pre-processing has completed
+ /// </summary>
+ /// <param name="root">The <see cref="IWebRoot"/> to process the event on</param>
+ /// <param name="ctx">The <see cref="HttpContext"/> to process</param>
+ /// <returns>A task that resolves when the user-code has completed processing the entity</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="TerminateConnectionException"></exception>
+ private static async ValueTask ProcessAsync(IWebRoot root, HttpContext ctx)
+ {
+ /*
+ * The event object should be cleared when it is no longer in use, IE before
+ * this procedure returns.
+ */
+ HttpEvent ev = new (ctx);
+ try
+ {
+ await root.ClientConnectedAsync(ev);
+ }
+ //User code requested exit, elevate the exception
+ catch (TerminateConnectionException)
+ {
+ throw;
+ }
+ //Transport exception
+ catch(IOException ioe) when (ioe.InnerException is SocketException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ ctx.ParentServer.Config.ServerLog.Warn(ex, "Unhandled exception during application code execution.");
+ }
+ finally
+ {
+ ev.Clear();
+ }
+ }
+
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/IConnectionContext.cs b/Net.Http/src/Core/IConnectionContext.cs
new file mode 100644
index 0000000..2e3ca46
--- /dev/null
+++ b/Net.Http/src/Core/IConnectionContext.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IConnectionContext.cs
+*
+* IConnectionContext.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// A request-response stream oriented connection state
+ /// </summary>
+ internal interface IConnectionContext
+ {
+ /// <summary>
+ /// Initializes the context to work with the specified
+ /// transport context
+ /// </summary>
+ /// <param name="tranpsort">A referrence to the transport context to use</param>
+ void InitializeContext(ITransportContext tranpsort);
+
+ /// <summary>
+ /// Signals the context that it should prepare to process a new request
+ /// for the current transport
+ /// </summary>
+ void BeginRequest();
+
+ /// <summary>
+ /// Sends any pending data associated with the request to the
+ /// connection that begun the request
+ /// </summary>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A Task that completes when the response has completed</returns>
+ Task WriteResponseAsync(CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Signals to the context that it will release any request specific
+ /// resources
+ /// </summary>
+ void EndRequest();
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/IHttpEvent.cs b/Net.Http/src/Core/IHttpEvent.cs
new file mode 100644
index 0000000..ec1dbb5
--- /dev/null
+++ b/Net.Http/src/Core/IHttpEvent.cs
@@ -0,0 +1,104 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpEvent.cs
+*
+* IHttpEvent.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Contains an http request and session information.
+ /// </summary>
+ public interface IHttpEvent
+ {
+ /// <summary>
+ /// Current connection information. (Like "$_SERVER" superglobal in PHP)
+ /// </summary>
+ IConnectionInfo Server { get; }
+ /// <summary>
+ /// The <see cref="HttpServer"/> that this connection originated from
+ /// </summary>
+ HttpServer OriginServer { get; }
+
+ /// <summary>
+ /// If the request has query arguments they are stored in key value format
+ /// </summary>
+ /// <remarks>Keys are case-insensitive</remarks>
+ IReadOnlyDictionary<string, string> QueryArgs { get; }
+ /// <summary>
+ /// If the request body has form data or url encoded arguments they are stored in key value format
+ /// </summary>
+ IReadOnlyDictionary<string, string> RequestArgs { get; }
+ /// <summary>
+ /// Contains all files upladed with current request
+ /// </summary>
+ /// <remarks>Keys are case-insensitive</remarks>
+ IReadOnlyList<FileUpload> Files { get; }
+
+ /// <summary>
+ /// Complete the session and respond to user
+ /// </summary>
+ /// <param name="code">Status code of operation</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code);
+
+ /// <summary>
+ /// Responds to a client with a <see cref="Stream"/> containing data to be sent to user of a given contentType.
+ /// Runtime will dispose of the stream during closing event
+ /// </summary>
+ /// <param name="code">Response status code</param>
+ /// <param name="type">MIME ContentType of data</param>
+ /// <param name="stream">Data to be sent to client</param>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code, ContentType type, Stream stream);
+
+ /// <summary>
+ /// Responds to a client with an in-memory <see cref="IMemoryResponseReader"/> containing data
+ /// to be sent to user of a given contentType.
+ /// </summary>
+ /// <param name="code">The status code to set</param>
+ /// <param name="type">The entity content-type</param>
+ /// <param name="entity">The in-memory response data</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity);
+
+ /// <summary>
+ /// Configures the server to change protocols from HTTP to the specified
+ /// custom protocol handler.
+ /// </summary>
+ /// <param name="protocolHandler">The custom protocol handler</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ void DangerousChangeProtocol(IAlternateProtocol protocolHandler);
+
+ /// <summary>
+ /// Disables response compression
+ /// </summary>
+ void DisableCompression();
+
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/IHttpLifeCycle.cs b/Net.Http/src/Core/IHttpLifeCycle.cs
new file mode 100644
index 0000000..135219d
--- /dev/null
+++ b/Net.Http/src/Core/IHttpLifeCycle.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpLifeCycle.cs
+*
+* IHttpLifeCycle.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Represents a interface of lifecycle hooks that correspond
+ /// with HTTP lifecycle events.
+ /// </summary>
+ internal interface IHttpLifeCycle
+ {
+ /// <summary>
+ /// Raised when the context is being prepare for reuse,
+ /// "revived from storage"
+ /// </summary>
+ void OnPrepare();
+
+ /// <summary>
+ /// Raised when the context is being released back to the pool
+ /// for reuse at a later time
+ /// </summary>
+ void OnRelease();
+
+ /// <summary>
+ /// Raised when a new request is about to be processed
+ /// on the current context
+ /// </summary>
+ void OnNewRequest();
+
+ /// <summary>
+ /// Raised when the request has been processed and the
+ /// response has been sent. Used to perform per-request
+ /// cleanup/reset for another request.
+ /// </summary>
+ /// <remarks>
+ /// This method is guarunteed to be called regardless of an http error, this
+ /// method should not throw exceptions
+ /// </remarks>
+ void OnComplete();
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/IHttpResponseBody.cs b/Net.Http/src/Core/IHttpResponseBody.cs
new file mode 100644
index 0000000..aa2dd34
--- /dev/null
+++ b/Net.Http/src/Core/IHttpResponseBody.cs
@@ -0,0 +1,73 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpResponseBody.cs
+*
+* IHttpResponseBody.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Represents a rseponse entity body
+ /// </summary>
+ internal interface IHttpResponseBody
+ {
+ /// <summary>
+ /// A value that indicates if there is data
+ /// to send to the client
+ /// </summary>
+ bool HasData { get; }
+
+ /// <summary>
+ /// A value that indicates if response data requires buffering
+ /// </summary>
+ bool BufferRequired { get; }
+
+ /// <summary>
+ /// Writes internal response entity data to the destination stream
+ /// </summary>
+ /// <param name="dest">The response stream to write data to</param>
+ /// <param name="buffer">An optional buffer used to buffer responses</param>
+ /// <param name="count">The maximum length of the response data to write</param>
+ /// <param name="token">A token to cancel the operation</param>
+ /// <returns>A task that resolves when the response is completed</returns>
+ Task WriteEntityAsync(Stream dest, long count, Memory<byte>? buffer, CancellationToken token);
+
+ /// <summary>
+ /// Writes internal response entity data to the destination stream
+ /// </summary>
+ /// <param name="dest">The response stream to write data to</param>
+ /// <param name="buffer">An optional buffer used to buffer responses</param>
+ /// <param name="token">A token to cancel the operation</param>
+ /// <returns>A task that resolves when the response is completed</returns>
+ Task WriteEntityAsync(Stream dest, Memory<byte>? buffer, CancellationToken token);
+
+ /// <summary>
+ /// The length of the content
+ /// </summary>
+ long Length { get; }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Request/HttpInputStream.cs b/Net.Http/src/Core/Request/HttpInputStream.cs
new file mode 100644
index 0000000..61d215f
--- /dev/null
+++ b/Net.Http/src/Core/Request/HttpInputStream.cs
@@ -0,0 +1,222 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpInputStream.cs
+*
+* HttpInputStream.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Specialized stream to allow reading a request entity body with a fixed content length.
+ /// </summary>
+ internal sealed class HttpInputStream : Stream
+ {
+ private readonly Func<Stream> GetTransport;
+
+ private long ContentLength;
+ private Stream? InputStream;
+ private long _position;
+
+ private ISlindingWindowBuffer<byte>? _initalData;
+
+ public HttpInputStream(Func<Stream> getTransport) => GetTransport = getTransport;
+
+ internal void OnComplete()
+ {
+ //Dispose the inigial data buffer
+ _initalData?.Close();
+ _initalData = null;
+ //Remove stream cache copy
+ InputStream = null;
+ //Reset position
+ _position = 0;
+ //reset content length
+ ContentLength = 0;
+ }
+
+ /// <summary>
+ /// Creates a new input stream object configured to allow reading of the specified content length
+ /// bytes from the stream and consumes the initial buffer to read data from on initial read calls
+ /// </summary>
+ /// <param name="contentLength">The number of bytes to allow being read from the transport or initial buffer</param>
+ /// <param name="initial">Entity body data captured on initial read</param>
+ internal void Prepare(long contentLength, ISlindingWindowBuffer<byte>? initial)
+ {
+ ContentLength = contentLength;
+ _initalData = initial;
+
+ //Cache transport
+ InputStream = GetTransport();
+ }
+
+ public override void Close() => throw new NotSupportedException("The HTTP input stream should never be closed!");
+ private long Remaining => Math.Max(ContentLength - _position, 0);
+ public override bool CanRead => true;
+ public override bool CanSeek => true;
+ public override bool CanWrite => false;
+ public override long Length => ContentLength;
+ public override long Position { get => _position; set { } }
+
+ public override void Flush(){}
+
+ public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count));
+ public override int Read(Span<byte> buffer)
+ {
+ //Calculate the amount of data that can be read into the buffer
+ int bytesToRead = (int)Math.Min(buffer.Length, Remaining);
+ if (bytesToRead == 0)
+ {
+ return 0;
+ }
+
+ //Clamp output buffer size and create buffer writer
+ ForwardOnlyWriter<byte> writer = new(buffer[..bytesToRead]);
+
+ //See if all data is internally buffered
+ if (_initalData != null && _initalData.AccumulatedSize > 0)
+ {
+ //Read as much as possible from internal buffer
+ ERRNO read = _initalData.Read(writer.Remaining);
+
+ //Advance writer
+ writer.Advance(read);
+
+ //Update position
+ _position += read;
+ }
+
+ //See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
+ if (writer.RemainingSize > 0)
+ {
+ //Read from transport
+ ERRNO read = InputStream!.Read(writer.Remaining);
+
+ //Update writer position
+ writer.Advance(read);
+
+ _position += read;
+ }
+
+ //Return number of bytes written to the buffer
+ return writer.Written;
+ }
+
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
+ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ //Calculate the amount of data that can be read into the buffer
+ int bytesToRead = (int)Math.Min(buffer.Length, Remaining);
+ if (bytesToRead == 0)
+ {
+ return 0;
+ }
+
+ //Clamp output buffer size and create buffer writer
+ ForwardOnlyMemoryWriter<byte> writer = new(buffer[..bytesToRead]);
+
+ //See if all data is internally buffered
+ if (_initalData != null && _initalData.AccumulatedSize > 0)
+ {
+ //Read as much as possible from internal buffer
+ ERRNO read = _initalData.Read(writer.Remaining.Span);
+
+ //Advance writer
+ writer.Advance(read);
+
+ //Update position
+ _position += read;
+ }
+
+ //See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
+ if (writer.RemainingSize > 0)
+ {
+ //Read from transport
+ ERRNO read = await InputStream!.ReadAsync(writer.Remaining, cancellationToken).ConfigureAwait(false);
+
+ //Update writer position
+ writer.Advance(read);
+
+ _position += read;
+ }
+
+ //Return number of bytes written to the buffer
+ return writer.Written;
+ }
+
+ /// <summary>
+ /// Asynchronously discards all remaining data in the stream
+ /// </summary>
+ /// <param name="heap">The heap to alloc buffers from</param>
+ /// <param name="maxBufferSize">The maxium size of the buffer to allocate</param>
+ /// <returns>A task that represents the discard operations</returns>
+ public async ValueTask DiscardRemainingAsync(int maxBufferSize)
+ {
+ long remaining = Remaining;
+ if(remaining == 0)
+ {
+ return;
+ }
+ //See if all data has already been buffered
+ if(_initalData != null && remaining <= _initalData.AccumulatedSize)
+ {
+ //All data has been buffred, so just clear the buffer
+ _position = Length;
+ }
+ //We must actaully disacrd data from the stream
+ else
+ {
+ //Calcuate a buffer size to allocate (will never be larger than an int)
+ int bufferSize = (int)Math.Min(remaining, maxBufferSize);
+ //Alloc a discard buffer to reset the transport
+ using IMemoryOwner<byte> discardBuffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+ int read = 0;
+ do
+ {
+ //Read data to the discard buffer until reading is completed
+ read = await ReadAsync(discardBuffer.Memory, CancellationToken.None).ConfigureAwait(true);
+
+ } while (read != 0);
+ }
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ //Ignore seek control
+ return _position;
+ }
+ public override void SetLength(long value) => throw new NotSupportedException();
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Request/HttpRequest.cs b/Net.Http/src/Core/Request/HttpRequest.cs
new file mode 100644
index 0000000..2410a8f
--- /dev/null
+++ b/Net.Http/src/Core/Request/HttpRequest.cs
@@ -0,0 +1,284 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpRequest.cs
+*
+* HttpRequest.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal class HttpRequest : IHttpLifeCycle
+#if DEBUG
+ ,IStringSerializeable
+#endif
+ {
+ public readonly VnWebHeaderCollection Headers;
+ public readonly Dictionary<string, string> Cookies;
+ public readonly List<string> Accept;
+ public readonly List<string> AcceptLanguage;
+ public readonly HttpRequestBody RequestBody;
+
+ public HttpVersion HttpVersion { get; set; }
+ public HttpMethod Method { get; set; }
+ public string? UserAgent { get; set; }
+ public string? Boundry { get; set; }
+ public ContentType ContentType { get; set; }
+ public string? Charset { get; set; }
+ public Uri Location { get; set; }
+ public Uri? Origin { get; set; }
+ public Uri? Referrer { get; set; }
+ internal bool KeepAlive { get; set; }
+ public IPEndPoint RemoteEndPoint { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; }
+ public SslProtocols EncryptionVersion { get; set; }
+ public Tuple<long, long>? Range { get; set; }
+ /// <summary>
+ /// A value indicating whether the connection contained a request entity body.
+ /// </summary>
+ public bool HasEntityBody { get; set; }
+ /// <summary>
+ /// A transport stream wrapper that is positioned for reading
+ /// the entity body from the input stream
+ /// </summary>
+ public HttpInputStream InputStream { get; }
+ /// <summary>
+ /// A value indicating if the client's request had an Expect-100-Continue header
+ /// </summary>
+ public bool Expect { get; set; }
+
+#nullable disable
+ public HttpRequest(Func<Stream> getTransport)
+ {
+ //Create new collection for headers
+ Headers = new();
+ //Create new collection for request cookies
+ Cookies = new();
+ //New list for accept
+ Accept = new();
+ AcceptLanguage = new();
+ //New reusable input stream
+ InputStream = new(getTransport);
+ RequestBody = new();
+ }
+
+
+ public void OnPrepare()
+ {}
+
+ public void OnRelease()
+ {}
+
+ public void OnNewRequest()
+ {
+ //Set to defaults
+ ContentType = ContentType.NonSupported;
+ EncryptionVersion = default;
+ Method = HttpMethod.NOT_SUPPORTED;
+ HttpVersion = HttpVersion.NotSupported;
+ }
+
+ public void OnComplete()
+ {
+ //release the input stream
+ InputStream.OnComplete();
+ RequestBody.OnComplete();
+ //Make sure headers, cookies, and accept are cleared for reuse
+ Headers.Clear();
+ Cookies.Clear();
+ Accept.Clear();
+ AcceptLanguage.Clear();
+ //Clear request flags
+ this.Expect = false;
+ this.KeepAlive = false;
+ this.HasEntityBody = false;
+ //We need to clean up object refs
+ this.Boundry = default;
+ this.Charset = default;
+ this.LocalEndPoint = default;
+ this.Location = default;
+ this.Origin = default;
+ this.Referrer = default;
+ this.RemoteEndPoint = default;
+ this.UserAgent = default;
+ this.Range = default;
+ //We are all set to reuse the instance
+ }
+
+
+#if DEBUG
+ public string Compile()
+ {
+ //Alloc char buffer for compilation
+ using UnsafeMemoryHandle<char> buffer = Memory.UnsafeAlloc<char>(16 * 1024, true);
+ ForwardOnlyWriter<char> writer = new(buffer.Span);
+ Compile(ref writer);
+ return writer.ToString();
+ }
+
+ public void Compile(ref ForwardOnlyWriter<char> writer)
+ {
+ //Request line
+ writer.Append(Method.ToString());
+ writer.Append(" ");
+ writer.Append(Location?.PathAndQuery);
+ writer.Append(" HTTP/");
+ switch (HttpVersion)
+ {
+ case HttpVersion.NotSupported:
+ writer.Append("Unsuppored Http version");
+ break;
+ case HttpVersion.Http1:
+ writer.Append("1.0");
+ break;
+ case HttpVersion.Http11:
+ writer.Append("1.1");
+ break;
+ case HttpVersion.Http2:
+ writer.Append("2.0");
+ break;
+ case HttpVersion.Http09:
+ writer.Append("0.9");
+ break;
+ }
+ writer.Append("\r\n");
+ //write host
+ writer.Append("Host: ");
+ writer.Append(Location?.Authority);
+ writer.Append("\r\n");
+
+ //Write headers
+ foreach (string header in Headers.Keys)
+ {
+ writer.Append(header);
+ writer.Append(": ");
+ writer.Append(Headers[header]);
+ writer.Append("\r\n");
+ }
+ //Write cookies
+ foreach (string cookie in Cookies.Keys)
+ {
+ writer.Append("Cookie: ");
+ writer.Append(cookie);
+ writer.Append("=");
+ writer.Append(Cookies[cookie]);
+ writer.Append("\r\n");
+ }
+
+ //Write accept
+ if (Accept.Count > 0)
+ {
+ writer.Append("Accept: ");
+ foreach (string accept in Accept)
+ {
+ writer.Append(accept);
+ writer.Append(", ");
+ }
+ writer.Append("\r\n");
+ }
+ //Write accept language
+ if (AcceptLanguage.Count > 0)
+ {
+ writer.Append("Accept-Language: ");
+ foreach (string acceptLanguage in AcceptLanguage)
+ {
+ writer.Append(acceptLanguage);
+ writer.Append(", ");
+ }
+ writer.Append("\r\n");
+ }
+ //Write user agent
+ if (UserAgent != null)
+ {
+ writer.Append("User-Agent: ");
+ writer.Append(UserAgent);
+ writer.Append("\r\n");
+ }
+ //Write content type
+ if (ContentType != ContentType.NonSupported)
+ {
+ writer.Append("Content-Type: ");
+ writer.Append(HttpHelpers.GetContentTypeString(ContentType));
+ writer.Append("\r\n");
+ }
+ //Write content length
+ if (ContentType != ContentType.NonSupported)
+ {
+ writer.Append("Content-Length: ");
+ writer.Append(InputStream.Length);
+ writer.Append("\r\n");
+ }
+ if (KeepAlive)
+ {
+ writer.Append("Connection: keep-alive\r\n");
+ }
+ if (Expect)
+ {
+ writer.Append("Expect: 100-continue\r\n");
+ }
+ if(Origin != null)
+ {
+ writer.Append("Origin: ");
+ writer.Append(Origin.ToString());
+ writer.Append("\r\n");
+ }
+ if (Referrer != null)
+ {
+ writer.Append("Referrer: ");
+ writer.Append(Referrer.ToString());
+ writer.Append("\r\n");
+ }
+ writer.Append("from ");
+ writer.Append(RemoteEndPoint.ToString());
+ writer.Append("\r\n");
+ writer.Append("Received on ");
+ writer.Append(LocalEndPoint.ToString());
+ //Write end of headers
+ writer.Append("\r\n");
+ }
+
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+ public override string ToString()
+ {
+ return Compile();
+ }
+#else
+ public override string ToString()
+ {
+ return "Request debug output only available when compiled in DEBUG mode";
+ }
+#endif
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Request/HttpRequestBody.cs b/Net.Http/src/Core/Request/HttpRequestBody.cs
new file mode 100644
index 0000000..824ca24
--- /dev/null
+++ b/Net.Http/src/Core/Request/HttpRequestBody.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpRequestBody.cs
+*
+* HttpRequestBody.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Represents a higher-level request entity body (query arguments, request body etc)
+ /// that has been parsed and captured
+ /// </summary>
+ internal class HttpRequestBody
+ {
+ public readonly List<FileUpload> Uploads;
+ public readonly Dictionary<string, string> RequestArgs;
+ public readonly Dictionary<string, string> QueryArgs;
+
+ public HttpRequestBody()
+ {
+ Uploads = new(1);
+
+ //Request/query args should not be request sensitive
+ RequestArgs = new(StringComparer.OrdinalIgnoreCase);
+ QueryArgs = new(StringComparer.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Releases all resources used by the current instance
+ /// </summary>
+ public void OnComplete()
+ {
+ //Only enumerate/clear if file uplaods are present
+ if (Uploads.Count > 0)
+ {
+ //Dispose all initialized files
+ for (int i = 0; i < Uploads.Count; i++)
+ {
+ Uploads[i].Free();
+ }
+ //Emtpy list
+ Uploads.Clear();
+ }
+ //Clear request args and file uplaods
+ RequestArgs.Clear();
+ QueryArgs.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/Net.Http/src/Core/Request/HttpRequestExtensions.cs
new file mode 100644
index 0000000..6a93192
--- /dev/null
+++ b/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -0,0 +1,304 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpRequestExtensions.cs
+*
+* HttpRequestExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http.Core
+{
+ internal static class HttpRequestExtensions
+ {
+ public enum CompressionType
+ {
+ None,
+ Gzip,
+ Deflate,
+ Brotli
+ }
+
+ /// <summary>
+ /// Gets the <see cref="CompressionType"/> that the connection accepts
+ /// in a default order, or none if not enabled
+ /// </summary>
+ /// <param name="request"></param>
+ /// <returns>A <see cref="CompressionType"/> with a value the connection support</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static CompressionType GetCompressionSupport(this HttpRequest request)
+ {
+ string? acceptEncoding = request.Headers[HttpRequestHeader.AcceptEncoding];
+
+ if (acceptEncoding == null)
+ {
+ return CompressionType.None;
+ }
+ else if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase))
+ {
+ return CompressionType.Gzip;
+ }
+ else if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase))
+ {
+ return CompressionType.Deflate;
+ }
+ else if (acceptEncoding.Contains("br", StringComparison.OrdinalIgnoreCase))
+ {
+ return CompressionType.Brotli;
+ }
+ else
+ {
+ return CompressionType.None;
+ }
+ }
+
+
+ /// <summary>
+ /// Tests the connection's origin header against the location URL by authority.
+ /// An origin matches if its scheme, host, and port match
+ /// </summary>
+ /// <returns>true if the origin header was set and does not match the current locations origin</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsCrossOrigin(this HttpRequest Request)
+ {
+ return Request.Origin != null
+ && (!Request.Origin.Authority.Equals(Request.Location.Authority, StringComparison.Ordinal)
+ || !Request.Origin.Scheme.Equals(Request.Location.Scheme, StringComparison.Ordinal));
+ }
+ /// <summary>
+ /// Is the current connection a websocket upgrade request handshake
+ /// </summary>
+ /// <returns>true if the connection is a websocket upgrade request, false otherwise</returns>
+ public static bool IsWebSocketRequest(this HttpRequest Request)
+ {
+ string? upgrade = Request.Headers[HttpRequestHeader.Upgrade];
+ if (!string.IsNullOrWhiteSpace(upgrade) && upgrade.Contains("websocket", StringComparison.OrdinalIgnoreCase))
+ {
+ //This request is a websocket request
+ //Check connection header
+ string? connection = Request.Headers[HttpRequestHeader.Connection];
+ //Must be a web socket request
+ return !string.IsNullOrWhiteSpace(connection) && connection.Contains("upgrade", StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Initializes the <see cref="HttpRequest"/> for an incomming connection
+ /// </summary>
+ /// <param name="server"></param>
+ /// <param name="ctx">The <see cref="TransportEventContext"/> to attach the request to</param>
+ /// <param name="defaultHttpVersion">The default http version</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Initialize(this HttpRequest server, ITransportContext ctx, HttpVersion defaultHttpVersion)
+ {
+ server.LocalEndPoint = ctx.LocalEndPoint;
+ server.RemoteEndPoint = ctx.RemoteEndpoint;
+ server.EncryptionVersion = ctx.SslVersion;
+ //Set to default http version so the response can be configured properly
+ server.HttpVersion = defaultHttpVersion;
+ }
+
+
+ /// <summary>
+ /// Initializes the <see cref="HttpRequest.RequestBody"/> for the current request
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="maxBufferSize">The maxium buffer size allowed while parsing reqeust body data</param>
+ /// <param name="encoding">The request data encoding for url encoded or form data bodies</param>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ internal static ValueTask InitRequestBodyAsync(this HttpRequest Request, int maxBufferSize, Encoding encoding)
+ {
+ /*
+ * Parses query parameters from the request location query
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void ParseQueryArgs(HttpRequest Request)
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ //Query string parse method
+ static void QueryParser(ReadOnlySpan<char> queryArgument, HttpRequest Request)
+ {
+ //Split spans after the '=' character
+ ReadOnlySpan<char> key = queryArgument.SliceBeforeParam('=');
+ ReadOnlySpan<char> value = queryArgument.SliceAfterParam('=');
+ //Insert into dict
+ Request.RequestBody.QueryArgs[key.ToString()] = value.ToString();
+ }
+
+ //if the request has query args, parse and store them
+ ReadOnlySpan<char> queryString = Request.Location.Query;
+ if (!queryString.IsEmpty)
+ {
+ //trim leading '?' if set
+ queryString = queryString.TrimStart('?');
+ //Split args by '&'
+ queryString.Split('&', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, QueryParser, Request);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static async ValueTask ParseInputStream(HttpRequest Request, int maxBufferSize, Encoding encoding)
+ {
+ /*
+ * Reads all available data from the request input stream
+ * If the stream size is smaller than a TCP buffer size, will complete synchronously
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static ValueTask<VnString> ReadInputStreamAsync(HttpRequest Request, int maxBufferSize, Encoding encoding)
+ {
+ //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
+ int bufferSize = (int)Math.Min(Request.InputStream.Length, maxBufferSize);
+ //Read the stream into a vnstring
+ return VnString.FromStreamAsync(Request.InputStream, encoding, HttpPrivateHeap, bufferSize);
+ }
+ /*
+ * SpanSplit callback function for storing UrlEncoded request entity
+ * bodies in key-value pairs and writing them to the
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void UrlEncodedSplitCb(ReadOnlySpan<char> kvArg, HttpRequest Request)
+ {
+ //Get key side of agument (or entire argument if no value is set)
+ ReadOnlySpan<char> key = kvArg.SliceBeforeParam('=');
+ ReadOnlySpan<char> value = kvArg.SliceAfterParam('=');
+ //trim, allocate strings, and store in the request arg dict
+ Request.RequestBody.RequestArgs[key.TrimCRLF().ToString()] = value.TrimCRLF().ToString();
+ }
+
+ /*
+ * Parses a Form-Data content type request entity body and stores those arguments in
+ * Request uploads or request args
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void FormDataBodySplitCb(ReadOnlySpan<char> formSegment, ValueTuple<HttpRequestBody, Encoding> state)
+ {
+ //Form data arguments
+ string? DispType = null, Name = null, FileName = null;
+ ContentType ctHeaderVal = ContentType.NonSupported;
+ //Get sliding window for parsing data
+ ForwardOnlyReader<char> reader = new(formSegment.TrimCRLF());
+ //Read content headers
+ do
+ {
+ //Get the index of the next crlf
+ int index = reader.Window.IndexOf(HttpHelpers.CRLF);
+ //end of headers
+ if (index < 1)
+ {
+ break;
+ }
+ //Get header data
+ ReadOnlySpan<char> header = reader.Window[..index];
+ //Split header at colon
+ int colon = header.IndexOf(':');
+ //If no data is available after the colon the header is not valid, so move on to the next body
+ if (colon < 1)
+ {
+ return;
+ }
+ //Hash the header value into a header enum
+ HttpRequestHeader headerType = HttpHelpers.GetRequestHeaderEnumFromValue(header[..colon]);
+ //get the header value
+ ReadOnlySpan<char> headerValue = header[(colon + 1)..];
+ //Check for content dispositon header
+ if (headerType == HttpHelpers.ContentDisposition)
+ {
+ //Parse the content dispostion
+ HttpHelpers.ParseDisposition(headerValue, out DispType, out Name, out FileName);
+ }
+ //Check for content type
+ else if (headerType == HttpRequestHeader.ContentType)
+ {
+ //The header value for content type should be an MIME content type
+ ctHeaderVal = HttpHelpers.GetContentType(headerValue.Trim().ToString());
+ }
+ //Shift window to the next line
+ reader.Advance(index + HttpHelpers.CRLF.Length);
+ } while (true);
+ //Remaining data should be the body data (will have leading and trailing CRLF characters
+ //If filename is set, this must be a file
+ if (!string.IsNullOrWhiteSpace(FileName))
+ {
+ //Store the file in the uploads
+ state.Item1.Uploads.Add(FileUpload.FromString(reader.Window.TrimCRLF(), state.Item2, FileName, ctHeaderVal));
+ }
+ //Make sure the name parameter was set and store the message body as a string
+ else if (!string.IsNullOrWhiteSpace(Name))
+ {
+ //String data as body
+ state.Item1.RequestArgs[Name] = reader.Window.TrimCRLF().ToString();
+ }
+ }
+
+ switch (Request.ContentType)
+ {
+ //CT not supported, dont read it
+ case ContentType.NonSupported:
+ break;
+ case ContentType.UrlEncoded:
+ //Create a vnstring from the message body and parse it (assuming url encoded bodies are small so a small stack buffer will be fine)
+ using (VnString urlbody = await ReadInputStreamAsync(Request, maxBufferSize, encoding))
+ {
+ //Get the body as a span, and split the 'string' at the & character
+ urlbody.AsSpan().Split('&', StringSplitOptions.RemoveEmptyEntries, UrlEncodedSplitCb, Request);
+ }
+ break;
+ case ContentType.MultiPart:
+ //Make sure we have a boundry specified
+ if (string.IsNullOrWhiteSpace(Request.Boundry))
+ {
+ break;
+ }
+ //Read all data from stream into string
+ using (VnString body = await ReadInputStreamAsync(Request, maxBufferSize, encoding))
+ {
+ //Split the body as a span at the boundries
+ body.AsSpan().Split($"--{Request.Boundry}", StringSplitOptions.RemoveEmptyEntries, FormDataBodySplitCb, (Request.RequestBody, encoding));
+ }
+ break;
+ //Default case is store as a file
+ default:
+ //add upload
+ Request.RequestBody.Uploads.Add(new(Request.InputStream, string.Empty, Request.ContentType, false));
+ break;
+ }
+ }
+
+ //Parse query
+ ParseQueryArgs(Request);
+
+ //Decode requests from body
+ return !Request.HasEntityBody ? ValueTask.CompletedTask : ParseInputStream(Request, maxBufferSize, encoding);
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs b/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
new file mode 100644
index 0000000..b37b78b
--- /dev/null
+++ b/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
@@ -0,0 +1,533 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: Http11ParseExtensions.cs
+*
+* Http11ParseExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Linq;
+using System.Collections.Generic;
+using System.Security.Authentication;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal static class Http11ParseExtensions
+ {
+
+ /// <summary>
+ /// Stores the state of an HTTP/1.1 parsing operation
+ /// </summary>
+ public ref struct Http1ParseState
+ {
+ internal UriBuilder? Location;
+ internal bool IsAbsoluteRequestUrl;
+ internal long ContentLength;
+ }
+
+
+ /// <summary>
+ /// Reads the first line from the transport stream using the specified buffer
+ /// and parses the HTTP request line components: Method, resource, Http Version
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="reader">The reader to read lines from the transport</param>
+ /// <param name="parseState">The HTTP1 parsing state</param>
+ /// <param name="lineBuf">The buffer to use when parsing the request data</param>
+ /// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
+ /// <exception cref="UriFormatException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static HttpStatusCode Http1ParseRequestLine(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in Span<char> lineBuf)
+ {
+ //Locals
+ ERRNO requestResult;
+ int index, endloc;
+
+ //Read the start line
+ requestResult = reader.ReadLine(lineBuf);
+ //Must be able to parse the verb and location
+ if (requestResult < 1)
+ {
+ //empty request
+ return (HttpStatusCode)1000;
+ }
+
+ //true up the request line to actual size
+ ReadOnlySpan<char> requestLine = lineBuf[..(int)requestResult].Trim();
+ //Find the first white space character ("GET / HTTP/1.1")
+ index = requestLine.IndexOf(' ');
+ if (index == -1)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Decode the verb (function requires the string be the exact characters of the request method)
+ Request.Method = HttpHelpers.GetRequestMethod(requestLine[0..index]);
+ //Make sure the method is supported
+ if (Request.Method == HttpMethod.NOT_SUPPORTED)
+ {
+ return HttpStatusCode.MethodNotAllowed;
+ }
+
+ //location string should be from end of verb to HTTP/ NOTE: Only supports http... this is an http server
+ endloc = requestLine.LastIndexOf(" HTTP/", StringComparison.OrdinalIgnoreCase);
+ //Client must specify an http version prepended by a single whitespace(rfc2612)
+ if (endloc == -1)
+ {
+ return HttpStatusCode.HttpVersionNotSupported;
+ }
+
+ //Try to parse the version and only accept the 3 major versions of http
+ Request.HttpVersion = HttpHelpers.ParseHttpVersion(requestLine[endloc..]);
+ //Check to see if the version was parsed succesfully
+ if (Request.HttpVersion == HttpVersion.NotSupported)
+ {
+ //Return not supported
+ return HttpStatusCode.HttpVersionNotSupported;
+ }
+
+ //Set keepalive flag if http11
+ Request.KeepAlive = Request.HttpVersion == HttpVersion.Http11;
+
+ //Get the location segment from the request line
+ ReadOnlySpan<char> paq = requestLine[(index + 1)..endloc].TrimCRLF();
+
+ //Process an absolute uri,
+ if (paq.Contains("://", StringComparison.Ordinal))
+ {
+ //Convert the location string to a .net string and init the location builder (will perform validation when the Uri propery is used)
+ parseState.Location = new(paq.ToString());
+ parseState.IsAbsoluteRequestUrl = true;
+ return 0;
+ }
+ //Try to capture a realative uri
+ else if (paq.Length > 0 && paq[0] == '/')
+ {
+ //Create a default location uribuilder
+ parseState.Location = new()
+ {
+ //Set a default scheme
+ Scheme = Request.EncryptionVersion == SslProtocols.None ? Uri.UriSchemeHttp : Uri.UriSchemeHttps,
+ };
+ //Need to manually parse the query string
+ int q = paq.IndexOf('?');
+ //has query?
+ if (q == -1)
+ {
+ parseState.Location.Path = paq.ToString();
+ }
+ //Does have query argument
+ else
+ {
+ //separate the path from the query
+ parseState.Location.Path = paq[0..q].ToString();
+ parseState.Location.Query = paq[(q + 1)..].ToString();
+ }
+ return 0;
+ }
+ //Cannot service an unknonw location
+ return HttpStatusCode.BadRequest;
+ }
+
+ /// <summary>
+ /// Reads headers from the transport using the supplied character buffer, and updates the current request
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="parseState">The HTTP1 parsing state</param>
+ /// <param name="Config">The current server <see cref="HttpConfig"/></param>
+ /// <param name="reader">The <see cref="VnStreamReader"/> to read lines from the transport</param>
+ /// <param name="lineBuf">The buffer read data from the transport with</param>
+ /// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static HttpStatusCode Http1ParseHeaders(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in HttpConfig Config, in Span<char> lineBuf)
+ {
+ try
+ {
+ int headerCount = 0, colon;
+ bool hostFound = false;
+ ERRNO charsRead;
+ ReadOnlySpan<char> headerName, requestHeaderValue;
+
+ /*
+ * This loop will read "lines" from the transport/reader buffer as headers
+ * and store them in the rented character buffer with 0 allocations.
+ *
+ * Lines will be read from the transport reader until an empty line is read,
+ * or an exception occurs. The VnStreamReader class will search for lines
+ * directly in the binary rather than converting the data then parsing it.
+ * When a line is parsed, its assumed to be an HTTP header at this point in
+ * the parsing, and is separated into its key-value pair determined by the
+ * first ':' character to appear.
+ *
+ * The header length will be limited by the size of the character buffer,
+ * or the reader binary buffer while reading lines. Buffer sizes are fixed
+ * to the system memory page size. Depending on the encoding the user chooses
+ * this should not be an issue for most configurations. This strategy is
+ * most efficient for realtivly small header sizes.
+ *
+ * The header's key is hashed by the HttpHelpers class and the hash is used to
+ * index a lookup table to return its enumeration value which is used in the swtich
+ * statement to reduce the number of strings added to the request header container.
+ * This was a major effort to reduce memory and CPU overhead while using the
+ * WebHeaderCollection .NET class, which I think is still worth using instead of a
+ * custom header data structure class.
+ *
+ * Some case statments are custom HttpRequestHeader enum values via internal casted
+ * constants to be consistant with he .NET implementation.
+ */
+ do
+ {
+ //Read a line until we reach the end of headers, this call will block if end of characters is reached and a new string will be read
+ charsRead = reader.ReadLine(lineBuf);
+
+ //If the result is less than 1, no line is available (end of headers) or could not be read
+ if (charsRead < 1)
+ {
+ break;
+ }
+
+ //Header count exceeded or header larger than header buffer size
+ if (charsRead < 0 || headerCount > Config.MaxRequestHeaderCount)
+ {
+ return HttpStatusCode.RequestHeaderFieldsTooLarge;
+ }
+
+ {
+ //Get the true size of the read header line as a readonly span
+ ReadOnlySpan<char> header = lineBuf[..(int)charsRead];
+
+ /*
+ * RFC 7230, ignore headers with preceeding whitespace
+ *
+ * If the first character is whitespace that is enough to
+ * ignore the rest of the header
+ */
+ if (header[0] == ' ')
+ {
+ //Move on to next header
+ continue;
+ }
+
+ //Find the first colon
+ colon = header.IndexOf(':');
+ //No colon was found, this is an invalid string, try to skip it and keep reading
+ if (colon <= 0)
+ {
+ continue;
+ }
+
+ //Store header and its value (sections before and after colon)
+ headerName = header[..colon].TrimCRLF();
+ requestHeaderValue = header[(colon + 1)..].TrimCRLF();
+ }
+
+ //Hash the header key and lookup the request header value
+ switch (HttpHelpers.GetRequestHeaderEnumFromValue(headerName))
+ {
+ case HttpRequestHeader.Connection:
+ {
+ //Update keepalive, if the connection header contains "closed" and with the current value of keepalive
+ Request.KeepAlive &= !requestHeaderValue.Contains("close", StringComparison.OrdinalIgnoreCase);
+ //Also store the connecion header into the store
+ Request.Headers.Add(HttpRequestHeader.Connection, requestHeaderValue.ToString());
+ }
+ break;
+ case HttpRequestHeader.ContentType:
+ {
+ if (!HttpHelpers.TryParseContentType(requestHeaderValue.ToString(), out string? ct, out string? charset, out string? boundry) || ct == null)
+ {
+ //Invalid content type header value
+ return HttpStatusCode.UnsupportedMediaType;
+ }
+ Request.Boundry = boundry;
+ Request.Charset = charset;
+ //Get the content type enum from mime type
+ Request.ContentType = HttpHelpers.GetContentType(ct);
+ }
+ break;
+ case HttpRequestHeader.ContentLength:
+ {
+ //Content length has already been calculated, ERROR, rfc 7230
+ if(parseState.ContentLength > 0)
+ {
+ Config.ServerLog.Debug("Message warning, recieved multiple content length headers");
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Only capture positive values, and if length is negative we are supposed to ignore it
+ if (ulong.TryParse(requestHeaderValue, out ulong len) && len < long.MaxValue)
+ {
+ parseState.ContentLength = (long)len;
+ }
+ else
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Request size it too large to service
+ if (parseState.ContentLength > Config.MaxUploadSize)
+ {
+ return HttpStatusCode.RequestEntityTooLarge;
+ }
+ }
+ break;
+ case HttpRequestHeader.Host:
+ {
+ //Set host found flag
+ hostFound = true;
+
+ //Split the host value by the port parameter
+ ReadOnlySpan<char> port = requestHeaderValue.SliceAfterParam(':').Trim();
+ //Slicing beofre the colon should always provide a useable hostname, so allocate a string for it
+ string host = requestHeaderValue.SliceBeforeParam(':').Trim().ToString();
+
+ //Verify that the host is usable
+ if (Uri.CheckHostName(host) == UriHostNameType.Unknown)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Verify that the host matches the host header if absolue uri is set
+ if (parseState.IsAbsoluteRequestUrl)
+ {
+ if (!host.Equals(parseState.Location!.Host, StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ }
+
+ //store the host value
+ parseState.Location!.Host = host;
+
+ //If the port span is empty, no colon was found or the port is invalid
+ if (!port.IsEmpty)
+ {
+ //try to parse the port number
+ if (!int.TryParse(port, out int p) || p < 0 || p > ushort.MaxValue)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ //Store port
+ parseState.Location.Port = p;
+ }
+ }
+ break;
+ case HttpRequestHeader.Cookie:
+ {
+ //Local function to break cookie segments into key-value pairs
+ static void AddCookiesCallback(ReadOnlySpan<char> cookie, Dictionary<string, string> cookieContainer)
+ {
+ //Get the name parameter and alloc a string
+ string name = cookie.SliceBeforeParam('=').Trim().ToString();
+ //Get the value parameter and alloc a string
+ string value = cookie.SliceAfterParam('=').Trim().ToString();
+ //Add the cookie to the dictionary
+ _ = cookieContainer.TryAdd(name, value);
+ }
+ //Split all cookies by ; with trailing whitespace
+ requestHeaderValue.Split("; ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, AddCookiesCallback, Request.Cookies);
+ }
+ break;
+ case HttpRequestHeader.AcceptLanguage:
+ //Capture accept languages and store in the request accept collection
+ requestHeaderValue.Split(',', Request.AcceptLanguage, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ break;
+ case HttpRequestHeader.Accept:
+ //Capture accept content types and store in request accept collection
+ requestHeaderValue.Split(',', Request.Accept, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ break;
+ case HttpRequestHeader.Referer:
+ {
+ //Check the referer header and capture its uri instance, it should be absolutely parseable
+ if (!requestHeaderValue.IsEmpty && Uri.TryCreate(requestHeaderValue.ToString(), UriKind.Absolute, out Uri? refer))
+ {
+ Request.Referrer = refer;
+ }
+ }
+ break;
+ case HttpRequestHeader.Range:
+ {
+ //See if range bytes value has been set
+ ReadOnlySpan<char> rawRange = requestHeaderValue.SliceAfterParam("bytes=").TrimCRLF();
+ //Make sure the bytes parameter is set
+ if (rawRange.IsEmpty)
+ {
+ break;
+ }
+ //Get start range
+ ReadOnlySpan<char> startRange = rawRange.SliceBeforeParam('-');
+ //Get end range (empty if no - exists)
+ ReadOnlySpan<char> endRange = rawRange.SliceAfterParam('-');
+ //See if a range end is specified
+ if (endRange.IsEmpty)
+ {
+ //No end range specified, so only range start
+ if (long.TryParse(startRange, out long start) && start > -1)
+ {
+ //Create new range
+ Request.Range = new(start, -1);
+ break;
+ }
+ }
+ //Range has a start and end
+ else if (long.TryParse(startRange, out long start) && long.TryParse(endRange, out long end) && end > -1)
+ {
+ //get start and end components from range header
+ Request.Range = new(start, end);
+ break;
+ }
+ }
+ //Could not parse start range from header
+ return HttpStatusCode.RequestedRangeNotSatisfiable;
+ case HttpRequestHeader.UserAgent:
+ //Store user-agent
+ Request.UserAgent = requestHeaderValue.IsEmpty ? string.Empty : requestHeaderValue.TrimCRLF().ToString();
+ break;
+ //Special code for origin header
+ case HttpHelpers.Origin:
+ {
+ //Alloc a string for origin
+ string origin = requestHeaderValue.ToString();
+ //Origin headers should always be absolute address "parsable"
+ if (Uri.TryCreate(origin, UriKind.Absolute, out Uri? org))
+ {
+ Request.Origin = org;
+ }
+ }
+ break;
+ case HttpRequestHeader.Expect:
+ //Accept 100-continue for the Expect header value
+ Request.Expect = requestHeaderValue.Equals("100-continue", StringComparison.OrdinalIgnoreCase);
+ break;
+ default:
+ //By default store the header in the request header store
+ Request.Headers.Add(headerName.ToString(), requestHeaderValue.ToString());
+ break;
+ }
+ //Increment header count
+ headerCount++;
+ } while (true);
+
+ //If request is http11 then host is required
+ if (Request.HttpVersion == HttpVersion.Http11 && !hostFound)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ }
+ //Catch an arugment exception within the header add function to cause a bad request result
+ catch (ArgumentException)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Check the final location to make sure data was properly sent
+ if (string.IsNullOrWhiteSpace(parseState.Location?.Host)
+ || string.IsNullOrWhiteSpace(parseState.Location.Scheme)
+ || string.IsNullOrWhiteSpace(parseState.Location.Path)
+ )
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Store the finalized location
+ Request.Location = parseState.Location.Uri;
+
+ return 0;
+ }
+ /// <summary>
+ /// Prepares the entity body for the current HTTP1 request
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="Config">The current server <see cref="HttpConfig"/></param>
+ /// <param name="parseState">The HTTP1 parsing state</param>
+ /// <param name="reader">The <see cref="VnStreamReader"/> to read lines from the transport</param>
+ /// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static HttpStatusCode Http1PrepareEntityBody(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in HttpConfig Config)
+ {
+ //If the content type is multipart, make sure its not too large to ingest
+ if (Request.ContentType == ContentType.MultiPart && parseState.ContentLength > Config.MaxFormDataUploadSize)
+ {
+ return HttpStatusCode.RequestEntityTooLarge;
+ }
+
+ //Only ingest the rest of the message body if the request is not a head, get, or trace methods
+ if ((Request.Method & (HttpMethod.GET | HttpMethod.HEAD | HttpMethod.TRACE)) != 0)
+ {
+ //Bad format to include a message body with a GET, HEAD, or TRACE request
+ if (parseState.ContentLength > 0)
+ {
+ Config.ServerLog.Debug("Message body received from {ip} with GET, HEAD, or TRACE request, was considered an error and the request was dropped", Request.RemoteEndPoint);
+ return HttpStatusCode.BadRequest;
+ }
+ else
+ {
+ //Success!
+ return 0;
+ }
+ }
+
+ //Check for chuncked transfer encoding
+ ReadOnlySpan<char> transfer = Request.Headers[HttpRequestHeader.TransferEncoding];
+ if (!transfer.IsEmpty && transfer.Contains("chunked", StringComparison.OrdinalIgnoreCase))
+ {
+ //Not a valid http version for chunked transfer encoding
+ if (Request.HttpVersion != HttpVersion.Http11)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ /*
+ * Was a content length also specified?
+ * This is an issue and is likely an attack. I am choosing not to support
+ * the HTTP 1.1 standard and will deny reading the rest of the data from the
+ * transport.
+ */
+ if (parseState.ContentLength > 0)
+ {
+ Config.ServerLog.Debug("Possible attempted desync, Content length + chunked encoding specified. RemoteEP: {ip}", Request.RemoteEndPoint);
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Handle chunked transfer encoding (not implemented yet)
+ return HttpStatusCode.NotImplemented;
+ }
+ //Make sure non-zero cl header was provided
+ else if (parseState.ContentLength > 0)
+ {
+ //Open a temp buffer to store initial data in
+ ISlindingWindowBuffer<byte>? initData = reader.GetReminaingData(parseState.ContentLength);
+ //Setup the input stream and capture the initial data from the reader, and wrap the transport stream to read data directly
+ Request.InputStream.Prepare(parseState.ContentLength, initData);
+ Request.HasEntityBody = true;
+ }
+ //Success!
+ return 0;
+ }
+ }
+}
diff --git a/Net.Http/src/Core/Response/ChunkDataAccumulator.cs b/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
new file mode 100644
index 0000000..35c0275
--- /dev/null
+++ b/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
@@ -0,0 +1,228 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ChunkDataAccumulator.cs
+*
+* ChunkDataAccumulator.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// A specialized <see cref="IDataAccumulator{T}"/> for buffering data
+ /// in Http/1.1 chunks
+ /// </summary>
+ internal class ChunkDataAccumulator : IDataAccumulator<byte>, IHttpLifeCycle
+ {
+ public const int RESERVED_CHUNK_SUGGESTION = 32;
+
+ private readonly int BufferSize;
+ private readonly int ReservedSize;
+ private readonly Encoding Encoding;
+ private readonly ReadOnlyMemory<byte> CRLFBytes;
+
+ public ChunkDataAccumulator(Encoding encoding, int bufferSize)
+ {
+ Encoding = encoding;
+ CRLFBytes = encoding.GetBytes(HttpHelpers.CRLF);
+
+ ReservedSize = RESERVED_CHUNK_SUGGESTION;
+ BufferSize = bufferSize;
+ }
+
+ private byte[]? _buffer;
+ private int _reservedOffset;
+
+
+ ///<inheritdoc/>
+ public int RemainingSize => _buffer!.Length - AccumulatedSize;
+ ///<inheritdoc/>
+ public Span<byte> Remaining => _buffer!.AsSpan(AccumulatedSize);
+ ///<inheritdoc/>
+ public Span<byte> Accumulated => _buffer!.AsSpan(_reservedOffset, AccumulatedSize);
+ ///<inheritdoc/>
+ public int AccumulatedSize { get; set; }
+
+ private Memory<byte> CompleteChunk => _buffer.AsMemory(_reservedOffset, (AccumulatedSize - _reservedOffset));
+
+ /// <summary>
+ /// Attempts to buffer as much data as possible from the specified data
+ /// </summary>
+ /// <param name="data">The data to copy</param>
+ /// <returns>The number of bytes that were buffered</returns>
+ public ERRNO TryBufferChunk(ReadOnlySpan<byte> data)
+ {
+ //Calc data size and reserve space for final crlf
+ int dataToCopy = Math.Min(data.Length, RemainingSize - CRLFBytes.Length);
+
+ //Write as much data as possible
+ data[..dataToCopy].CopyTo(Remaining);
+ //Advance buffer
+ Advance(dataToCopy);
+
+ //Return number of bytes not written
+ return dataToCopy;
+ }
+
+ ///<inheritdoc/>
+ public void Advance(int count) => AccumulatedSize += count;
+
+ private void InitReserved()
+ {
+ //First reserve the chunk window by advancing the accumulator to the size
+ Advance(ReservedSize);
+ }
+
+ ///<inheritdoc/>
+ public void Reset()
+ {
+ //zero offsets
+ _reservedOffset = 0;
+ AccumulatedSize = 0;
+ //Init reserved segment
+ InitReserved();
+ }
+
+ /// <summary>
+ /// Writes the buffered data as a single chunk to the stream asynchronously. The internal
+ /// state is reset if writing compleded successfully
+ /// </summary>
+ /// <param name="output">The stream to write data to</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A value task that resolves when the data has been written to the stream</returns>
+ public async ValueTask FlushAsync(Stream output, CancellationToken cancellation)
+ {
+ //Update the chunk size
+ UpdateChunkSize();
+
+ //Write trailing chunk delimiter
+ this.Append(CRLFBytes.Span);
+
+ //write to stream
+ await output.WriteAsync(CompleteChunk, cancellation);
+
+ //Reset for next chunk
+ Reset();
+ }
+
+ /// <summary>
+ /// Writes the buffered data as a single chunk to the stream. The internal
+ /// state is reset if writing compleded successfully
+ /// </summary>
+ /// <param name="output">The stream to write data to</param>
+ /// <returns>A value task that resolves when the data has been written to the stream</returns>
+ public void Flush(Stream output)
+ {
+ //Update the chunk size
+ UpdateChunkSize();
+
+ //Write trailing chunk delimiter
+ this.Append(CRLFBytes.Span);
+
+ //write to stream
+ output.Write(CompleteChunk.Span);
+
+ //Reset for next chunk
+ Reset();
+ }
+
+ private void UpdateChunkSize()
+ {
+ const int CharBufSize = 2 * sizeof(int);
+
+ /*
+ * Alloc stack buffer to store chunk size hex chars
+ * the size of the buffer should be at least the number
+ * of bytes of the max chunk size
+ */
+ Span<char> s = stackalloc char[CharBufSize];
+
+ //Chunk size is the accumulated size without the reserved segment
+ int chunkSize = (AccumulatedSize - ReservedSize);
+
+ //format the chunk size
+ chunkSize.TryFormat(s, out int written, "x");
+
+ //temp buffer to store encoded data in
+ Span<byte> encBuf = stackalloc byte[ReservedSize];
+ //Encode the chunk size chars
+ int initOffset = Encoding.GetBytes(s[..written], encBuf);
+
+ Span<byte> encoded = encBuf[..initOffset];
+
+ /*
+ * We need to calcuate how to store the encoded buffer directly
+ * before the accumulated chunk data.
+ *
+ * This requires us to properly upshift the reserved buffer to
+ * the exact size required to store the encoded chunk size
+ */
+
+ _reservedOffset = (ReservedSize - (initOffset + CRLFBytes.Length));
+
+ Span<byte> upshifted = _buffer!.AsSpan(_reservedOffset, ReservedSize);
+
+ //First write the chunk size
+ encoded.CopyTo(upshifted);
+
+ //Upshift again to write the crlf
+ upshifted = upshifted[initOffset..];
+
+ //Copy crlf
+ CRLFBytes.Span.CopyTo(upshifted);
+ }
+
+
+ public void OnNewRequest()
+ {
+ InitReserved();
+ }
+
+ public void OnComplete()
+ {
+ //Zero offsets
+ _reservedOffset = 0;
+ AccumulatedSize = 0;
+ }
+
+ public void OnPrepare()
+ {
+ //Alloc buffer
+ _buffer = HttpBinBufferPool.Rent(BufferSize);
+ }
+
+ public void OnRelease()
+ {
+ HttpBinBufferPool.Return(_buffer!);
+ _buffer = null;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Response/ChunkedStream.cs b/Net.Http/src/Core/Response/ChunkedStream.cs
new file mode 100644
index 0000000..7a8bebc
--- /dev/null
+++ b/Net.Http/src/Core/Response/ChunkedStream.cs
@@ -0,0 +1,249 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ChunkedStream.cs
+*
+* ChunkedStream.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+* Provides a Chunked data-encoding stream for writing data-chunks to
+* the transport using the basic chunked encoding format from MDN
+* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding#directives
+*
+* This stream will buffer entire chunks to avoid multiple writes to the
+* transport which can block or at minium cause overhead in context switching
+* which should be mostly avoided but cause overhead in copying. Time profiling
+* showed nearly equivalent performance for small chunks for synchronous writes.
+*
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal partial class HttpResponse
+ {
+ /// <summary>
+ /// Writes chunked HTTP message bodies to an underlying streamwriter
+ /// </summary>
+ private class ChunkedStream : Stream, IHttpLifeCycle
+ {
+ private const string LAST_CHUNK_STRING = "0\r\n\r\n";
+
+ private readonly ReadOnlyMemory<byte> LastChunk;
+ private readonly ChunkDataAccumulator ChunckAccumulator;
+ private readonly Func<Stream> GetTransport;
+
+ private Stream? TransportStream;
+ private bool HadError;
+
+ internal ChunkedStream(Encoding encoding, int chunkBufferSize, Func<Stream> getStream)
+ {
+ //Convert and store cached versions of the last chunk bytes
+ LastChunk = encoding.GetBytes(LAST_CHUNK_STRING);
+
+ //get the min buffer by rounding to the nearest page
+ int actualBufSize = (chunkBufferSize / 4096 + 1) * 4096;
+
+ //Init accumulator
+ ChunckAccumulator = new(encoding, actualBufSize);
+
+ GetTransport = getStream;
+ }
+
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+ public override long Length => throw new NotSupportedException();
+ public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("This stream cannot be read from");
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException("This stream does not support seeking");
+ public override void SetLength(long value) => throw new NotSupportedException("This stream does not support seeking");
+ public override void Flush() { }
+ public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+
+ public override void Write(byte[] buffer, int offset, int count) => Write(new ReadOnlySpan<byte>(buffer, offset, count));
+ public override void Write(ReadOnlySpan<byte> chunk)
+ {
+ //Only write non-zero chunks
+ if (chunk.Length <= 0)
+ {
+ return;
+ }
+
+ //Init reader
+ ForwardOnlyReader<byte> reader = new(in chunk);
+ try
+ {
+ do
+ {
+ //try to accumulate the chunk data
+ ERRNO written = ChunckAccumulator.TryBufferChunk(reader.Window);
+
+ //Not all data was buffered
+ if (written < reader.WindowSize)
+ {
+ //Advance reader
+ reader.Advance(written);
+
+ //Flush accumulator
+ ChunckAccumulator.Flush(TransportStream!);
+ //Continue to buffer / flush as needed
+ continue;
+ }
+ break;
+ }
+ while (true);
+ }
+ catch
+ {
+ HadError = true;
+ throw;
+ }
+ }
+
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
+
+ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> chunk, CancellationToken cancellationToken = default)
+ {
+ //Only write non-zero chunks
+ if (chunk.Length <= 0)
+ {
+ return;
+ }
+
+ try
+ {
+ //Init reader
+ ForwardOnlyMemoryReader<byte> reader = new(in chunk);
+
+ do
+ {
+ //try to accumulate the chunk data
+ ERRNO written = ChunckAccumulator.TryBufferChunk(reader.Window.Span);
+
+ //Not all data was buffered
+ if (written < reader.WindowSize)
+ {
+ //Advance reader
+ reader.Advance(written);
+
+ //Flush accumulator async
+ await ChunckAccumulator.FlushAsync(TransportStream!, cancellationToken);
+ //Continue to buffer / flush as needed
+ continue;
+ }
+ break;
+ }
+ while (true);
+ }
+ catch
+ {
+ HadError = true;
+ throw;
+ }
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ //If write error occured, then do not write the last chunk
+ if (HadError)
+ {
+ return;
+ }
+
+ //Write remaining data to stream
+ await ChunckAccumulator.FlushAsync(TransportStream!, CancellationToken.None);
+
+ //Write final chunk
+ await TransportStream!.WriteAsync(LastChunk, CancellationToken.None);
+
+ //Flush base stream
+ await TransportStream!.FlushAsync(CancellationToken.None);
+ }
+
+ protected override void Dispose(bool disposing) => Close();
+
+ public override void Close()
+ {
+ //If write error occured, then do not write the last chunk
+ if (HadError)
+ {
+ return;
+ }
+
+ //Write remaining data to stream
+ ChunckAccumulator.Flush(TransportStream!);
+
+ //Write final chunk
+ TransportStream!.Write(LastChunk.Span);
+
+ //Flush base stream
+ TransportStream!.Flush();
+ }
+
+
+ #region Hooks
+
+ public void OnPrepare()
+ {
+ ChunckAccumulator.OnPrepare();
+ }
+
+ public void OnRelease()
+ {
+ ChunckAccumulator.OnRelease();
+ }
+
+ public void OnNewRequest()
+ {
+ ChunckAccumulator.OnNewRequest();
+
+ //Get transport stream even if not used
+ TransportStream = GetTransport();
+ }
+
+ public void OnComplete()
+ {
+ ChunckAccumulator.OnComplete();
+ TransportStream = null;
+
+ //Clear error flag
+ HadError = false;
+ }
+
+ #endregion
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Response/DirectStream.cs b/Net.Http/src/Core/Response/DirectStream.cs
new file mode 100644
index 0000000..957c2a6
--- /dev/null
+++ b/Net.Http/src/Core/Response/DirectStream.cs
@@ -0,0 +1,96 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: DirectStream.cs
+*
+* DirectStream.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core
+{
+ internal partial class HttpResponse
+ {
+ private class DirectStream : Stream
+ {
+ private Stream? BaseStream;
+
+ public void Prepare(Stream transport)
+ {
+ BaseStream = transport;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ BaseStream!.Write(buffer, offset, count);
+ }
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ BaseStream!.Write(buffer);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return BaseStream!.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return BaseStream!.WriteAsync(buffer, cancellationToken);
+ }
+
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+ public override long Length => throw new InvalidOperationException("Stream does not have a length property");
+ public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException("Stream does not support reading");
+ public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException("Stream does not support seeking");
+ public override void SetLength(long value) => throw new InvalidOperationException("Stream does not support seeking");
+
+ public override void Flush() => BaseStream!.Flush();
+ public override Task FlushAsync(CancellationToken cancellationToken) => BaseStream!.FlushAsync(cancellationToken);
+
+
+ public override void Close()
+ {
+ BaseStream = null;
+ }
+
+
+ protected override void Dispose(bool disposing)
+ {
+ //Do not call base dispose
+ Close();
+ }
+
+ public override ValueTask DisposeAsync()
+ {
+ Close();
+ return ValueTask.CompletedTask;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Response/HeaderDataAccumulator.cs b/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
new file mode 100644
index 0000000..715871f
--- /dev/null
+++ b/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
@@ -0,0 +1,157 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HeaderDataAccumulator.cs
+*
+* HeaderDataAccumulator.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http.Core
+{
+ internal partial class HttpResponse
+ {
+
+ /// <summary>
+ /// Specialized data accumulator for compiling response headers
+ /// </summary>
+ private class HeaderDataAccumulator : IDataAccumulator<char>, IStringSerializeable, IHttpLifeCycle
+ {
+ private readonly int BufferSize;
+
+ public HeaderDataAccumulator(int bufferSize)
+ {
+ //Calc correct char buffer size from bin buffer
+ this.BufferSize = bufferSize * sizeof(char);
+ }
+
+ /*
+ * May be an issue but wanted to avoid alloc
+ * if possible since this is a field in a ref
+ * type
+ */
+
+ private UnsafeMemoryHandle<byte>? _handle;
+
+ public void Advance(int count)
+ {
+ //Advance writer
+ AccumulatedSize += count;
+ }
+
+ public void WriteLine() => this.Append(HttpHelpers.CRLF);
+
+ public void WriteLine(ReadOnlySpan<char> data)
+ {
+ this.Append(data);
+ WriteLine();
+ }
+
+ /*Use bin buffers and cast to char buffer*/
+ private Span<char> Buffer => MemoryMarshal.Cast<byte, char>(_handle!.Value.Span);
+
+ public int RemainingSize => Buffer.Length - AccumulatedSize;
+ public Span<char> Remaining => Buffer[AccumulatedSize..];
+ public Span<char> Accumulated => Buffer[..AccumulatedSize];
+ public int AccumulatedSize { get; set; }
+
+ /// <summary>
+ /// Encodes the buffered data and writes it to the stream,
+ /// attemts to avoid further allocation where possible
+ /// </summary>
+ /// <param name="enc"></param>
+ /// <param name="baseStream"></param>
+ public void Flush(Encoding enc, Stream baseStream)
+ {
+ ReadOnlySpan<char> span = Accumulated;
+ //Calc the size of the binary buffer
+ int byteSize = enc.GetByteCount(span);
+ //See if there is enough room in the current char buffer
+ if (RemainingSize < (byteSize / sizeof(char)))
+ {
+ //We need to alloc a binary buffer to write data to
+ using UnsafeMemoryHandle<byte> bin = GetBinBuffer(byteSize, false);
+ //encode data
+ int encoded = enc.GetBytes(span, bin.Span);
+ //Write to stream
+ baseStream.Write(bin.Span[..encoded]);
+ }
+ else
+ {
+ //Get bin buffer by casting remaining accumulator buffer
+ Span<byte> bin = MemoryMarshal.Cast<char, byte>(Remaining);
+ //encode data
+ int encoded = enc.GetBytes(span, bin);
+ //Write to stream
+ baseStream.Write(bin[..encoded]);
+ }
+ Reset();
+ }
+
+ public void Reset() => AccumulatedSize = 0;
+
+
+
+ public void OnPrepare()
+ {
+ //Alloc buffer
+ _handle = GetBinBuffer(BufferSize, false);
+ }
+
+ public void OnRelease()
+ {
+ _handle!.Value.Dispose();
+ _handle = null;
+ }
+
+ public void OnNewRequest()
+ {}
+
+ public void OnComplete()
+ {
+ Reset();
+ }
+
+
+ ///<inheritdoc/>
+ public string Compile() => Accumulated.ToString();
+ ///<inheritdoc/>
+ public void Compile(ref ForwardOnlyWriter<char> writer) => writer.Append(Accumulated);
+ ///<inheritdoc/>
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+ ///<inheritdoc/>
+ public override string ToString() => Compile();
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Response/HttpContextExtensions.cs b/Net.Http/src/Core/Response/HttpContextExtensions.cs
new file mode 100644
index 0000000..12702b3
--- /dev/null
+++ b/Net.Http/src/Core/Response/HttpContextExtensions.cs
@@ -0,0 +1,124 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpContextExtensions.cs
+*
+* HttpContextExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Provides extended funcionality of an <see cref="HttpContext"/>
+ /// </summary>
+ internal static class HttpContextExtensions
+ {
+ /// <summary>
+ /// Responds to a connection with the given status code
+ /// </summary>
+ /// <param name="ctx"></param>
+ /// <param name="code">The status code to send</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static void Respond(this HttpContext ctx, HttpStatusCode code) => ctx.Response.SetStatusCode(code);
+
+ /// <summary>
+ /// Begins a 301 redirection by sending status code and message heaaders to client.
+ /// </summary>
+ /// <param name="ctx"></param>
+ /// <param name="location">Location to direct client to, sets the "Location" header</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static void Redirect301(this HttpContext ctx, Uri location)
+ {
+ ctx.Response.SetStatusCode(HttpStatusCode.MovedPermanently);
+ //Encode the string for propery http url formatting and set the location header
+ ctx.Response.Headers[HttpResponseHeader.Location] = location.ToString();
+ }
+
+ public const string NO_CACHE_STRING = "no-cache";
+ private static readonly string CACHE_CONTROL_VALUE = HttpHelpers.GetCacheString(CacheType.NoCache | CacheType.NoStore);
+
+ /// <summary>
+ /// Sets CacheControl and Pragma headers to no-cache
+ /// </summary>
+ /// <param name="Response"></param>
+ public static void SetNoCache(this HttpResponse Response)
+ {
+ Response.Headers[HttpResponseHeader.Pragma] = NO_CACHE_STRING;
+ Response.Headers[HttpResponseHeader.CacheControl] = CACHE_CONTROL_VALUE;
+ }
+
+ /// <summary>
+ /// Sets the content-range header to the specified parameters
+ /// </summary>
+ /// <param name="Response"></param>
+ /// <param name="start">The content range start</param>
+ /// <param name="end">The content range end</param>
+ /// <param name="length">The total content length</param>
+ public static void SetContentRange(this HttpResponse Response, long start, long end, long length)
+ {
+ //Alloc enough space to hold the string
+ Span<char> buffer = stackalloc char[64];
+ ForwardOnlyWriter<char> rangeBuilder = new(buffer);
+ //Build the range header in this format "bytes <begin>-<end>/<total>"
+ rangeBuilder.Append("bytes ");
+ rangeBuilder.Append(start);
+ rangeBuilder.Append('-');
+ rangeBuilder.Append(end);
+ rangeBuilder.Append('/');
+ rangeBuilder.Append(length);
+ //Print to a string and set the content range header
+ Response.Headers[HttpResponseHeader.ContentRange] = rangeBuilder.ToString();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ReadOnlyMemory<byte> GetRemainingConstrained(this IMemoryResponseReader reader, int limit)
+ {
+ //Calc the remaining bytes
+ int size = Math.Min(reader.Remaining, limit);
+ //get segment and slice
+ return reader.GetMemory()[..size];
+ }
+
+ /// <summary>
+ /// If an end-range is set, returns the remaining bytes up to the end-range, otherwise returns the entire request body length
+ /// </summary>
+ /// <param name="body"></param>
+ /// <param name="range">The data range</param>
+ /// <returns></returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static long GetResponseLengthWithRange(this IHttpResponseBody body, Tuple<long, long> range)
+ {
+ /*
+ * If end range is defined, then calculate the length of the response
+ *
+ * The length is the end range minus the start range plus 1 because range
+ * is an inclusve value
+ */
+
+ return range.Item2 < 0 ? body.Length : Math.Min(body.Length, range.Item2 - range.Item1 + 1);
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Response/HttpContextResponseWriting.cs b/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
new file mode 100644
index 0000000..b03363e
--- /dev/null
+++ b/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
@@ -0,0 +1,253 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpContextResponseWriting.cs
+*
+* HttpContextResponseWriting.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.IO.Compression;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal partial class HttpContext
+ {
+ ///<inheritdoc/>
+ public async Task WriteResponseAsync(CancellationToken cancellation)
+ {
+ /*
+ * If exceptions are raised, the transport is unusable, the connection is terminated,
+ * and the release method will be called so the context can be reused
+ */
+
+ ValueTask discardTask = Request.InputStream.DiscardRemainingAsync(ParentServer.Config.DiscardBufferSize);
+
+ //See if discard is needed
+ if (ResponseBody.HasData)
+ {
+ //Parallel the write and discard
+ Task response = WriteResponseInternalAsync(cancellation);
+ Task discard = discardTask.AsTask();
+
+ await Task.WhenAll(response, discard);
+ }
+ else
+ {
+ await discardTask;
+ }
+
+ //Close response
+ await Response.CloseAsync();
+ }
+
+ /// <summary>
+ /// If implementing application set a response entity body, it is written to the output stream
+ /// </summary>
+ /// <param name="token">A token to cancel the operation</param>
+ private async Task WriteResponseInternalAsync(CancellationToken token)
+ {
+ //Adjust/append vary header
+ Response.Headers.Add(HttpResponseHeader.Vary, "Accept-Encoding");
+
+ //For head methods
+ if (Request.Method == HttpMethod.HEAD)
+ {
+ if (Request.Range != null)
+ {
+ //Get local range
+ Tuple<long, long> range = Request.Range;
+
+ //Calc constrained content length
+ long length = ResponseBody.GetResponseLengthWithRange(range);
+
+ //End range is inclusive so substract 1
+ long endRange = (range.Item1 + length) - 1;
+
+ //Set content-range header
+ Response.SetContentRange(range.Item1, endRange, length);
+
+ //Specify what the content length would be
+ Response.Headers[HttpResponseHeader.ContentLength] = length.ToString();
+
+ }
+ else
+ {
+ //If the request method is head, do everything but send the body
+ Response.Headers[HttpResponseHeader.ContentLength] = ResponseBody.Length.ToString();
+ }
+
+ //We must send headers here so content length doesnt get overwritten
+ Response.FlushHeaders();
+ }
+ else
+ {
+ Stream outputStream;
+ /*
+ * Process range header, data will not be compressed because that would
+ * require buffering, not a feature yet, and since the range will tell
+ * us the content length, we can get a direct stream to write to
+ */
+ if (Request.Range != null)
+ {
+ //Get local range
+ Tuple<long, long> range = Request.Range;
+
+ //Calc constrained content length
+ long length = ResponseBody.GetResponseLengthWithRange(range);
+
+ //End range is inclusive so substract 1
+ long endRange = (range.Item1 + length) - 1;
+
+ //Set content-range header
+ Response.SetContentRange(range.Item1, endRange, length);
+
+ //Get the raw output stream and set the length to the number of bytes
+ outputStream = Response.GetStream(length);
+
+ await WriteEntityDataAsync(outputStream, length, token);
+ }
+ else
+ {
+ //Determine if compression should be used
+ bool compressionDisabled =
+ //disabled because app code disabled it
+ ContextFlags.IsSet(COMPRESSION_DISABLED_MSK)
+ //Disabled because too large or too small
+ || ResponseBody.Length >= ParentServer.Config.CompressionLimit
+ || ResponseBody.Length < ParentServer.Config.CompressionMinimum
+ //Disabled because lower than http11 does not support chunked encoding
+ || Request.HttpVersion < HttpVersion.Http11;
+
+ //Get first compression method or none if disabled
+ HttpRequestExtensions.CompressionType ct = compressionDisabled ? HttpRequestExtensions.CompressionType.None : Request.GetCompressionSupport();
+
+ switch (ct)
+ {
+ case HttpRequestExtensions.CompressionType.Gzip:
+ {
+ //Specify gzip encoding (using chunked encoding)
+ Response.Headers[HttpResponseHeader.ContentEncoding] = "gzip";
+ //get the chunked output stream
+ Stream chunked = Response.GetStream();
+ //Use chunked encoding and send data as its written
+ outputStream = new GZipStream(chunked, ParentServer.Config.CompressionLevel, false);
+ }
+ break;
+ case HttpRequestExtensions.CompressionType.Deflate:
+ {
+ //Specify gzip encoding (using chunked encoding)
+ Response.Headers[HttpResponseHeader.ContentEncoding] = "deflate";
+ //get the chunked output stream
+ Stream chunked = Response.GetStream();
+ //Use chunked encoding and send data as its written
+ outputStream = new DeflateStream(chunked, ParentServer.Config.CompressionLevel, false);
+ }
+ break;
+ case HttpRequestExtensions.CompressionType.Brotli:
+ {
+ //Specify Brotli encoding (using chunked encoding)
+ Response.Headers[HttpResponseHeader.ContentEncoding] = "br";
+ //get the chunked output stream
+ Stream chunked = Response.GetStream();
+ //Use chunked encoding and send data as its written
+ outputStream = new BrotliStream(chunked, ParentServer.Config.CompressionLevel, false);
+ }
+ break;
+ //Default is no compression
+ case HttpRequestExtensions.CompressionType.None:
+ default:
+ //Since we know how long the response will be, we can submit it now (see note above for same issues)
+ outputStream = Response.GetStream(ResponseBody.Length);
+ break;
+ }
+
+ //Write entity to output
+ await WriteEntityDataAsync(outputStream, token);
+ }
+ }
+ }
+
+ private async Task WriteEntityDataAsync(Stream outputStream, CancellationToken token)
+ {
+ try
+ {
+ //Determine if buffer is required
+ if (ResponseBody.BufferRequired)
+ {
+ //Calc a buffer size (always a safe cast since rbs is an integer)
+ int bufferSize = (int)Math.Min((long)ParentServer.Config.ResponseBufferSize, ResponseBody.Length);
+
+ //Alloc buffer, and dispose when completed
+ using IMemoryOwner<byte> buffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+
+ //Write response
+ await ResponseBody.WriteEntityAsync(outputStream, buffer.Memory, token);
+ }
+ //No buffer is required, write response directly
+ else
+ {
+ //Write without buffer
+ await ResponseBody.WriteEntityAsync(outputStream, null, token);
+ }
+ }
+ finally
+ {
+ //always dispose output stream
+ await outputStream.DisposeAsync();
+ }
+ }
+
+ private async Task WriteEntityDataAsync(Stream outputStream, long length, CancellationToken token)
+ {
+ try
+ {
+ //Determine if buffer is required
+ if (ResponseBody.BufferRequired)
+ {
+ //Calc a buffer size (always a safe cast since rbs is an integer)
+ int bufferSize = (int)Math.Min((long)ParentServer.Config.ResponseBufferSize, ResponseBody.Length);
+
+ //Alloc buffer, and dispose when completed
+ using IMemoryOwner<byte> buffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+
+ //Write response
+ await ResponseBody.WriteEntityAsync(outputStream, length, buffer.Memory, token);
+ }
+ //No buffer is required, write response directly
+ else
+ {
+ //Write without buffer
+ await ResponseBody.WriteEntityAsync(outputStream, length, null, token);
+ }
+ }
+ finally
+ {
+ //always dispose output stream
+ await outputStream.DisposeAsync();
+ }
+ }
+ }
+}
diff --git a/Net.Http/src/Core/Response/HttpResponse.cs b/Net.Http/src/Core/Response/HttpResponse.cs
new file mode 100644
index 0000000..ab0971d
--- /dev/null
+++ b/Net.Http/src/Core/Response/HttpResponse.cs
@@ -0,0 +1,307 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpResponse.cs
+*
+* HttpResponse.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.IO;
+
+namespace VNLib.Net.Http.Core
+{
+ internal partial class HttpResponse : IHttpLifeCycle
+ {
+ private readonly HashSet<HttpCookie> Cookies;
+ private readonly HeaderDataAccumulator Writer;
+
+ private readonly DirectStream ReusableDirectStream;
+ private readonly ChunkedStream ReusableChunkedStream;
+ private readonly Func<Stream> _getStream;
+ private readonly Encoding ResponseEncoding;
+ private readonly Func<HttpVersion> GetVersion;
+
+ private bool HeadersSent;
+ private bool HeadersBegun;
+
+ private HttpStatusCode _code;
+
+ /// <summary>
+ /// Response header collection
+ /// </summary>
+ public VnWebHeaderCollection Headers { get; }
+
+ public HttpResponse(Encoding encoding, int headerBufferSize, int chunkedBufferSize, Func<Stream> getStream, Func<HttpVersion> getVersion)
+ {
+ //Initialize a new header collection and a cookie jar
+ Headers = new();
+ Cookies = new();
+ //Create a new reusable writer stream
+ Writer = new(headerBufferSize);
+
+ _getStream = getStream;
+ ResponseEncoding = encoding;
+
+ //Create a new chunked stream
+ ReusableChunkedStream = new(encoding, chunkedBufferSize, getStream);
+ ReusableDirectStream = new();
+ GetVersion = getVersion;
+ }
+
+
+ /// <summary>
+ /// Sets the status code of the response
+ /// </summary>
+ /// <exception cref="InvalidOperationException"></exception>
+ internal void SetStatusCode(HttpStatusCode code)
+ {
+ if (HeadersBegun)
+ {
+ throw new InvalidOperationException("Status code has already been sent");
+ }
+
+ _code = code;
+ }
+
+ /// <summary>
+ /// Adds a new http-cookie to the collection
+ /// </summary>
+ /// <param name="cookie">Cookie to add</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void AddCookie(HttpCookie cookie) => Cookies.Add(cookie);
+
+ /// <summary>
+ /// Allows sending an early 100-Continue status message to the client
+ /// </summary>
+ /// <exception cref="InvalidOperationException"></exception>
+ internal async Task SendEarly100ContinueAsync()
+ {
+ Check();
+ //Send a status message with the continue response status
+ Writer.WriteLine(HttpHelpers.GetResponseString(GetVersion(), HttpStatusCode.Continue));
+ //Trailing crlf
+ Writer.WriteLine();
+ //get base stream
+ Stream bs = _getStream();
+ //Flush writer to stream (will reset the buffer)
+ Writer.Flush(ResponseEncoding, bs);
+ //Flush the base stream
+ await bs.FlushAsync();
+ }
+
+ /// <summary>
+ /// Sends the status message and all available headers to the client.
+ /// Headers set after method returns will be sent when output stream is requested or scope exits
+ /// </summary>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public void FlushHeaders()
+ {
+ Check();
+ //If headers havent been sent yet, start with status code
+ if (!HeadersBegun)
+ {
+ //write status code first
+ Writer.WriteLine(HttpHelpers.GetResponseString(GetVersion(), _code));
+
+ //Write the date to header buffer
+ Writer.Append("Date: ");
+ Writer.Append(DateTimeOffset.UtcNow, "R");
+ Writer.WriteLine();
+ //Set begun flag
+ HeadersBegun = true;
+ }
+ //Write headers
+ for (int i = 0; i < Headers.Count; i++)
+ {
+ Writer.Append(Headers.Keys[i]); //Write header key
+ Writer.Append(": "); //Write separator
+ Writer.WriteLine(Headers[i]); //Write the header value
+ }
+ //Remove writen headers
+ Headers.Clear();
+ //Write cookies if any are set
+ if (Cookies.Count > 0)
+ {
+ //Write cookies if any have been set
+ foreach (HttpCookie cookie in Cookies)
+ {
+ Writer.Append("Set-Cookie: ");
+ Writer.Append(in cookie);
+ Writer.WriteLine();
+ }
+ //Clear all current cookies
+ Cookies.Clear();
+ }
+ }
+ private void EndFlushHeaders(Stream transport)
+ {
+ //Sent all available headers
+ FlushHeaders();
+ //Last line to end headers
+ Writer.WriteLine();
+
+ //Flush writer
+ Writer.Flush(ResponseEncoding, transport);
+ //Update sent headers
+ HeadersSent = true;
+ }
+
+ /// <summary>
+ /// Gets a stream for writing data of a specified length directly to the client
+ /// </summary>
+ /// <param name="ContentLength"></param>
+ /// <returns>A <see cref="Stream"/> configured for writing data to client</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public Stream GetStream(long ContentLength)
+ {
+ Check();
+ //Add content length header
+ Headers[HttpResponseHeader.ContentLength] = ContentLength.ToString();
+ //End sending headers so the user can write to the ouput stream
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+
+ //Init direct stream
+ ReusableDirectStream.Prepare(transport);
+
+ //Return the direct stream
+ return ReusableDirectStream;
+ }
+
+ /// <summary>
+ /// Sets up the client for chuncked encoding and gets a stream that allows for chuncks to be sent. User must call dispose on stream when done writing data
+ /// </summary>
+ /// <returns><see cref="Stream"/> supporting chunked encoding</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public Stream GetStream()
+ {
+#if DEBUG
+ if (GetVersion() != HttpVersion.Http11)
+ {
+ throw new InvalidOperationException("Chunked transfer encoding is not acceptable for this http version");
+ }
+#endif
+ Check();
+ //Set encoding type to chunked with user-defined compression
+ Headers[HttpResponseHeader.TransferEncoding] = "chunked";
+ //End sending headers so the user can write to the ouput stream
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+
+ //Return the reusable stream
+ return ReusableChunkedStream;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void Check()
+ {
+ if (HeadersSent)
+ {
+ throw new InvalidOperationException("Headers have already been sent!");
+ }
+ }
+
+ /// <summary>
+ /// Finalzies the response to a client by sending all available headers if
+ /// they have not been sent yet
+ /// </summary>
+ /// <exception cref="OutOfMemoryException"></exception>
+ internal async ValueTask CloseAsync()
+ {
+ //If headers havent been sent yet, send them and there must be no content
+ if (!HeadersBegun)
+ {
+ //RFC 7230, length only set on 200 + but not 204
+ if ((int)_code >= 200 && (int)_code != 204)
+ {
+ //If headers havent been sent by this stage there is no content, so set length to 0
+ Headers[HttpResponseHeader.ContentLength] = "0";
+ }
+ //Flush transport
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+ //Flush transport
+ await transport.FlushAsync();
+ }
+ //Headers have been started but not finished yet
+ else if (!HeadersSent)
+ {
+ //RFC 7230, length only set on 200 + but not 204
+ if ((int)_code >= 200 && (int)_code != 204)
+ {
+ //If headers havent been sent by this stage there is no content, so set length to 0
+ Headers[HttpResponseHeader.ContentLength] = "0";
+ }
+ //If headers arent done sending yet, conclude headers
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+ //Flush transport
+ await transport.FlushAsync();
+ }
+ }
+
+
+ public void OnPrepare()
+ {
+ //Propagate all child lifecycle hooks
+ Writer.OnPrepare();
+ ReusableChunkedStream.OnPrepare();
+ }
+
+ public void OnRelease()
+ {
+ Writer.OnRelease();
+ ReusableChunkedStream.OnRelease();
+ }
+
+ public void OnNewRequest()
+ {
+ //Default to okay status code
+ _code = HttpStatusCode.OK;
+
+ Writer.OnNewRequest();
+ ReusableChunkedStream.OnNewRequest();
+ }
+
+ public void OnComplete()
+ {
+ //Clear headers and cookies
+ Headers.Clear();
+ Cookies.Clear();
+ //Reset status values
+ HeadersBegun = false;
+ HeadersSent = false;
+
+ //Call child lifecycle hooks
+ Writer.OnComplete();
+ ReusableChunkedStream.OnComplete();
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/Response/ResponseWriter.cs b/Net.Http/src/Core/Response/ResponseWriter.cs
new file mode 100644
index 0000000..c9f20b5
--- /dev/null
+++ b/Net.Http/src/Core/Response/ResponseWriter.cs
@@ -0,0 +1,182 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ResponseWriter.cs
+*
+* ResponseWriter.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Extensions;
+
+
+namespace VNLib.Net.Http.Core
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "<Pending>")]
+ internal sealed class ResponseWriter : IHttpResponseBody, IHttpLifeCycle
+ {
+ private Stream? _streamResponse;
+ private IMemoryResponseReader? _memoryResponse;
+
+ ///<inheritdoc/>
+ public bool HasData { get; private set; }
+
+ //Buffering is required when a stream is set
+ bool IHttpResponseBody.BufferRequired => _streamResponse != null;
+
+ ///<inheritdoc/>
+ public long Length { get; private set; }
+
+ /// <summary>
+ /// Attempts to set the response body as a stream
+ /// </summary>
+ /// <param name="response">The stream response body to read</param>
+ /// <returns>True if the response entity could be set, false if it has already been set</returns>
+ internal bool TrySetResponseBody(Stream response)
+ {
+ if (HasData)
+ {
+ return false;
+ }
+
+ //Get relative length of the stream, IE the remaning bytes in the stream if position has been modified
+ Length = (response.Length - response.Position);
+ //Store ref to stream
+ _streamResponse = response;
+ //update has-data flag
+ HasData = true;
+ return true;
+ }
+
+ /// <summary>
+ /// Attempts to set the response entity
+ /// </summary>
+ /// <param name="response">The memory response to set</param>
+ /// <returns>True if the response entity could be set, false if it has already been set</returns>
+ internal bool TrySetResponseBody(IMemoryResponseReader response)
+ {
+ if (HasData)
+ {
+ return false;
+ }
+
+ //Get length
+ Length = response.Remaining;
+ //Store ref to stream
+ _memoryResponse = response;
+ //update has-data flag
+ HasData = true;
+ return true;
+ }
+
+ ///<inheritdoc/>
+ async Task IHttpResponseBody.WriteEntityAsync(Stream dest, long count, Memory<byte>? buffer, CancellationToken token)
+ {
+ //Write a sliding window response
+ if (_memoryResponse != null)
+ {
+ //Get min value from count/range length
+ int remaining = (int)Math.Min(count, _memoryResponse.Remaining);
+
+ //Write response body from memory
+ while (remaining > 0)
+ {
+ //Get remaining segment
+ ReadOnlyMemory<byte> segment = _memoryResponse.GetRemainingConstrained(remaining);
+
+ //Write segment to output stream
+ await dest.WriteAsync(segment, token);
+
+ int written = segment.Length;
+
+ //Advance by the written ammount
+ _memoryResponse.Advance(written);
+
+ //Update remaining
+ remaining -= written;
+ }
+ }
+ else
+ {
+ //Buffer is required, and count must be supplied
+ await _streamResponse!.CopyToAsync(dest, buffer!.Value, count, token);
+ }
+ }
+
+ ///<inheritdoc/>
+ async Task IHttpResponseBody.WriteEntityAsync(Stream dest, Memory<byte>? buffer, CancellationToken token)
+ {
+ //Write a sliding window response
+ if (_memoryResponse != null)
+ {
+ //Write response body from memory
+ while (_memoryResponse.Remaining > 0)
+ {
+ //Get segment
+ ReadOnlyMemory<byte> segment = _memoryResponse.GetMemory();
+
+ await dest.WriteAsync(segment, token);
+
+ //Advance by
+ _memoryResponse.Advance(segment.Length);
+ }
+ }
+ else
+ {
+ //Buffer is required
+ await _streamResponse!.CopyToAsync(dest, buffer!.Value, token);
+
+ //Try to dispose the response stream
+ await _streamResponse!.DisposeAsync();
+
+ //remove ref
+ _streamResponse = null;
+ }
+ }
+
+ ///<inheritdoc/>
+ void IHttpLifeCycle.OnPrepare()
+ {}
+
+ ///<inheritdoc/>
+ void IHttpLifeCycle.OnRelease()
+ {}
+
+ ///<inheritdoc/>
+ void IHttpLifeCycle.OnNewRequest()
+ {}
+
+ public void OnComplete()
+ {
+ //Clear has data flag
+ HasData = false;
+ Length = 0;
+
+ //Clear rseponse containers
+ _streamResponse?.Dispose();
+ _streamResponse = null;
+ _memoryResponse?.Close();
+ _memoryResponse = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/SharedHeaderReaderBuffer.cs b/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
new file mode 100644
index 0000000..36ebb66
--- /dev/null
+++ b/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: SharedHeaderReaderBuffer.cs
+*
+* SharedHeaderReaderBuffer.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils.Memory;
+
+
+
+namespace VNLib.Net.Http.Core
+{
+ sealed class SharedHeaderReaderBuffer : IHttpLifeCycle
+ {
+ private UnsafeMemoryHandle<byte>? Handle;
+
+ /// <summary>
+ /// The size of the binary buffer
+ /// </summary>
+ public int BinLength { get; }
+
+ private readonly int _bufferSize;
+
+ internal SharedHeaderReaderBuffer(int length)
+ {
+ _bufferSize = length + (length * sizeof(char));
+
+ //Bin buffer is the specified size
+ BinLength = length;
+ }
+
+ /// <summary>
+ /// The binary buffer to store reader information
+ /// </summary>
+ public Span<byte> BinBuffer => Handle!.Value.Span[..BinLength];
+
+ /// <summary>
+ /// The char buffer to store read characters in
+ /// </summary>
+ public Span<char> CharBuffer => MemoryMarshal.Cast<byte, char>(Handle!.Value.Span[BinLength..]);
+
+ public void OnPrepare()
+ {
+ //Alloc the shared buffer
+ Handle = CoreBufferHelpers.GetBinBuffer(_bufferSize, true);
+ }
+
+ public void OnRelease()
+ {
+ //Free buffer
+ Handle?.Dispose();
+ Handle = null;
+ }
+
+ public void OnNewRequest()
+ {}
+
+ public void OnComplete()
+ {
+ //Zero buffer
+ Handle!.Value.Span.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Core/VnHeaderCollection.cs b/Net.Http/src/Core/VnHeaderCollection.cs
new file mode 100644
index 0000000..8ce3c88
--- /dev/null
+++ b/Net.Http/src/Core/VnHeaderCollection.cs
@@ -0,0 +1,75 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: VnHeaderCollection.cs
+*
+* VnHeaderCollection.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed class VnHeaderCollection : IHeaderCollection
+ {
+ private VnWebHeaderCollection _RequestHeaders;
+ private VnWebHeaderCollection _ResponseHeaders;
+
+
+ IEnumerable<KeyValuePair<string, string>> IHeaderCollection.RequestHeaders => _RequestHeaders!;
+
+ IEnumerable<KeyValuePair<string, string>> IHeaderCollection.ResponseHeaders => _ResponseHeaders!;
+
+ internal VnHeaderCollection(HttpContext context)
+ {
+ _RequestHeaders = context.Request.Headers;
+ _ResponseHeaders = context.Response.Headers;
+ }
+
+ string? IHeaderCollection.this[string index]
+ {
+ get => _RequestHeaders[index];
+ set => _ResponseHeaders[index] = value;
+ }
+
+ string IHeaderCollection.this[HttpResponseHeader index]
+ {
+ set => _ResponseHeaders[index] = value;
+ }
+
+ string? IHeaderCollection.this[HttpRequestHeader index] => _RequestHeaders[index];
+
+ bool IHeaderCollection.HeaderSet(HttpResponseHeader header) => !string.IsNullOrEmpty(_ResponseHeaders[header]);
+
+ bool IHeaderCollection.HeaderSet(HttpRequestHeader header) => !string.IsNullOrEmpty(_RequestHeaders[header]);
+
+ void IHeaderCollection.Append(HttpResponseHeader header, string? value) => _ResponseHeaders.Add(header, value);
+
+ void IHeaderCollection.Append(string header, string? value) => _ResponseHeaders.Add(header, value);
+
+#nullable disable
+ internal void Clear()
+ {
+ _RequestHeaders = null;
+ _ResponseHeaders = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Exceptions/ContentTypeException.cs b/Net.Http/src/Exceptions/ContentTypeException.cs
new file mode 100644
index 0000000..abff151
--- /dev/null
+++ b/Net.Http/src/Exceptions/ContentTypeException.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ContentTypeException.cs
+*
+* ContentTypeException.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Thrown when the application attempts to submit a response to a client
+ /// when the client does not accept the given content type
+ /// </summary>
+ public sealed class ContentTypeUnacceptableException:FormatException
+ {
+ public ContentTypeUnacceptableException(string message) : base(message) {}
+
+ public ContentTypeUnacceptableException()
+ {}
+
+ public ContentTypeUnacceptableException(string message, Exception innerException) : base(message, innerException)
+ {}
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Exceptions/TerminateConnectionException.cs b/Net.Http/src/Exceptions/TerminateConnectionException.cs
new file mode 100644
index 0000000..b854b6e
--- /dev/null
+++ b/Net.Http/src/Exceptions/TerminateConnectionException.cs
@@ -0,0 +1,57 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TerminateConnectionException.cs
+*
+* TerminateConnectionException.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// User code may throw this exception to signal the <see cref="HttpServer"/> to drop
+ /// the transport connection and return an optional status code
+ /// </summary>
+ public class TerminateConnectionException : Exception
+ {
+ internal HttpStatusCode Code { get; }
+
+ /// <summary>
+ /// Creates a new instance that terminates the connection without sending a response to the connection
+ /// </summary>
+ public TerminateConnectionException() : base(){}
+ /// <summary>
+ /// Creates a new instance of the connection exception with an error code to respond to the connection with
+ /// </summary>
+ /// <param name="responseCode">The status code to send to the user</param>
+ public TerminateConnectionException(HttpStatusCode responseCode)
+ {
+ this.Code = responseCode;
+ }
+
+ public TerminateConnectionException(string message) : base(message)
+ {}
+
+ public TerminateConnectionException(string message, Exception innerException) : base(message, innerException)
+ {}
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/FileUpload.cs b/Net.Http/src/FileUpload.cs
new file mode 100644
index 0000000..654d682
--- /dev/null
+++ b/Net.Http/src/FileUpload.cs
@@ -0,0 +1,122 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: FileUpload.cs
+*
+* FileUpload.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents an file that was received as an entity body, either using Multipart/FormData or as the entity body itself
+ /// </summary>
+ public readonly struct FileUpload
+ {
+ /// <summary>
+ /// Content type of uploaded file
+ /// </summary>
+ public readonly ContentType ContentType;
+ /// <summary>
+ /// Name of file uploaded
+ /// </summary>
+ public readonly string FileName;
+ /// <summary>
+ /// The file data captured on upload
+ /// </summary>
+ public readonly Stream FileData;
+
+ private readonly bool OwnsHandle;
+
+ /// <summary>
+ /// Allocates a new binary buffer, encodes, and copies the specified data to a new <see cref="FileUpload"/>
+ /// structure of the specified content type
+ /// </summary>
+ /// <param name="data">The string data to copy</param>
+ /// <param name="dataEncoding">The encoding instance to encode the string data from</param>
+ /// <param name="filename">The name of the file</param>
+ /// <param name="ct">The content type of the file data</param>
+ /// <returns>The <see cref="FileUpload"/> container</returns>
+ internal static FileUpload FromString(ReadOnlySpan<char> data, Encoding dataEncoding, string filename, ContentType ct)
+ {
+ //get number of bytes
+ int bytes = dataEncoding.GetByteCount(data);
+ //get a buffer from the HTTP heap
+ MemoryHandle<byte> buffHandle = HttpPrivateHeap.Alloc<byte>(bytes);
+ try
+ {
+ //Convert back to binary
+ bytes = dataEncoding.GetBytes(data, buffHandle);
+
+ //Create a new memory stream encapsulating the file data
+ VnMemoryStream vms = VnMemoryStream.ConsumeHandle(buffHandle, bytes, true);
+
+ //Create new upload wrapper
+ return new (vms, filename, ct, true);
+ }
+ catch
+ {
+ //Make sure the hanle gets disposed if there is an error
+ buffHandle.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Initialzes a new <see cref="FileUpload"/> structure from the specified data
+ /// and file information.
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="filename"></param>
+ /// <param name="ct"></param>
+ /// <param name="ownsHandle"></param>
+ public FileUpload(Stream data, string filename, ContentType ct, bool ownsHandle)
+ {
+ FileName = filename;
+ ContentType = ct;
+ //Store handle ownership
+ OwnsHandle = ownsHandle;
+ //Store the stream
+ FileData = data;
+ }
+
+ /// <summary>
+ /// Releases any memory the current instance holds if it owns the handles
+ /// </summary>
+ internal readonly void Free()
+ {
+ //Dispose the handle if we own it
+ if (OwnsHandle)
+ {
+ //This should always be synchronous
+ FileData.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs b/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs
new file mode 100644
index 0000000..88891fb
--- /dev/null
+++ b/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs
@@ -0,0 +1,50 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: AlternateProtocolTransportStreamWrapper.cs
+*
+* AlternateProtocolTransportStreamWrapper.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+using VNLib.Utils.IO;
+
+namespace VNLib.Net.Http.Core
+{
+ internal class AlternateProtocolTransportStreamWrapper : BackingStream<Stream>
+ {
+ public AlternateProtocolTransportStreamWrapper(Stream transport)
+ {
+ this.BaseStream = transport;
+ }
+
+ //Do not allow the caller to dispose the transport stream
+ protected override void Dispose(bool disposing)
+ {}
+ public override ValueTask DisposeAsync()
+ {
+ return ValueTask.CompletedTask;
+ }
+ public override void Close()
+ {}
+ }
+}
diff --git a/Net.Http/src/Helpers/ContentType.cs b/Net.Http/src/Helpers/ContentType.cs
new file mode 100644
index 0000000..ce7b7ce
--- /dev/null
+++ b/Net.Http/src/Helpers/ContentType.cs
@@ -0,0 +1,1180 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ContentType.cs
+*
+* ContentType.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Mime content type
+ /// </summary>
+ public enum ContentType
+ {
+ NonSupported,
+ MultiPart,
+ UrlEncoded,
+ Aab,
+ Aac,
+ Aam,
+ Aas,
+ Abw,
+ Ac,
+ Acc,
+ Ace,
+ Acu,
+ Acutc,
+ Adp,
+ Aep,
+ Afm,
+ Afp,
+ Ahead,
+ Ai,
+ Aif,
+ Aifc,
+ Aiff,
+ Air,
+ Ait,
+ Ami,
+ Amr,
+ Apk,
+ Apng,
+ Appcache,
+ Apr,
+ Arc,
+ Arj,
+ Asc,
+ Asf,
+ Asm,
+ Aso,
+ Asx,
+ Atc,
+ Atom,
+ Atomcat,
+ Atomsvc,
+ Atx,
+ Au,
+ Avi,
+ Avif,
+ Aw,
+ Azf,
+ Azs,
+ Azv,
+ Azw,
+ B16,
+ Bat,
+ Bcpio,
+ Bdf,
+ Bdm,
+ Bdoc,
+ Bed,
+ Bh2,
+ Binary,
+ Blb,
+ Blorb,
+ Bmi,
+ Bmml,
+ Bmp,
+ Book,
+ Box,
+ Boz,
+ Bpk,
+ Bsp,
+ Btif,
+ Buffer,
+ Bz,
+ Bz2,
+ C,
+ C11amc,
+ C11amz,
+ C4d,
+ C4f,
+ C4g,
+ C4p,
+ C4u,
+ Cab,
+ Caf,
+ Cap,
+ Car,
+ Cat,
+ Cb7,
+ Cba,
+ Cbr,
+ Cbt,
+ Cbz,
+ Cc,
+ Cco,
+ Cct,
+ Ccxml,
+ Cdbcmsg,
+ Cdf,
+ Cdfx,
+ Cdkey,
+ Cdmia,
+ Cdmic,
+ Cdmid,
+ Cdmio,
+ Cdmiq,
+ Cdx,
+ Cdxml,
+ Cdy,
+ Cer,
+ Cfs,
+ Cgm,
+ Chat,
+ Chm,
+ Chrt,
+ Cif,
+ Cii,
+ Cil,
+ Cjs,
+ Cla,
+ Clkk,
+ Clkp,
+ Clkt,
+ Clkw,
+ Clkx,
+ Clp,
+ Cmc,
+ Cmdf,
+ Cml,
+ Cmp,
+ Cmx,
+ Cod,
+ Coffee,
+ Com,
+ Conf,
+ Cpio,
+ Cpp,
+ Cpt,
+ Crd,
+ Crl,
+ Crt,
+ Crx,
+ Csh,
+ Csl,
+ Csml,
+ Csp,
+ Css,
+ Cst,
+ Csv,
+ Cu,
+ Curl,
+ Cww,
+ Cxt,
+ Cxx,
+ Dae,
+ Daf,
+ Dart,
+ Dataless,
+ Davmount,
+ Dbf,
+ Dbk,
+ Dcr,
+ Dcurl,
+ Dd2,
+ Ddd,
+ Ddf,
+ Dds,
+ Deb,
+ Def,
+ Deploy,
+ Der,
+ Dfac,
+ Dgc,
+ Dic,
+ Dir,
+ Dis,
+ Dist,
+ Distz,
+ Djv,
+ Djvu,
+ Dll,
+ Dmg,
+ Dmp,
+ Dms,
+ Dna,
+ Doc,
+ Docm,
+ Docx,
+ Dot,
+ Dotm,
+ Dotx,
+ Dp,
+ Dpg,
+ Dra,
+ Drle,
+ Dsc,
+ Dssc,
+ Dtb,
+ Dtd,
+ Dts,
+ Dtshd,
+ Dump,
+ Dvb,
+ Dvi,
+ Dwd,
+ Dwf,
+ Dwg,
+ Dxf,
+ Dxp,
+ Dxr,
+ Ear,
+ Ecma,
+ Edm,
+ Edx,
+ Efif,
+ Ei6,
+ Elc,
+ Emf,
+ Eml,
+ Emma,
+ Emz,
+ Eol,
+ Eot,
+ Eps,
+ Epub,
+ Es,
+ Es3,
+ Esa,
+ Esf,
+ Et3,
+ Etx,
+ Eva,
+ Evy,
+ Exe,
+ Exi,
+ Exp,
+ Exr,
+ Ext,
+ Ez,
+ Ez2,
+ Ez3,
+ F,
+ F4v,
+ Fortran,
+ F90,
+ Fbs,
+ Fcdt,
+ Fcs,
+ Fdf,
+ Fdt,
+ Fg5,
+ Fgd,
+ Fh,
+ Fh4,
+ Fh5,
+ Fh7,
+ Fhc,
+ Fig,
+ Fits,
+ Flac,
+ Fli,
+ Flo,
+ Flv,
+ Flw,
+ Flx,
+ Fly,
+ Fm,
+ Fnc,
+ Fo,
+ For,
+ Fpx,
+ Frame,
+ Fsc,
+ Fst,
+ Ftc,
+ Fti,
+ Fvt,
+ Fxp,
+ Fxpl,
+ Fzs,
+ G2w,
+ G3,
+ G3w,
+ Gac,
+ Gam,
+ Gbr,
+ Gca,
+ Gdl,
+ Gdoc,
+ Geo,
+ Geojson,
+ Gex,
+ Ggb,
+ Ggt,
+ Ghf,
+ Gif,
+ Gim,
+ Glb,
+ Gltf,
+ Gml,
+ Gmx,
+ Gnumeric,
+ Gph,
+ Gpx,
+ Gqf,
+ Gqs,
+ Gram,
+ Gramps,
+ Gre,
+ Grv,
+ Grxml,
+ Gsf,
+ Gsheet,
+ Gslides,
+ Gtar,
+ Gtm,
+ Gtw,
+ Gv,
+ Gxf,
+ Gxt,
+ Gz,
+ H,
+ H261,
+ H263,
+ H264,
+ Hal,
+ Hbci,
+ Hbs,
+ Hdd,
+ Hdf,
+ Heic,
+ Heics,
+ Heif,
+ Heifs,
+ Hej2,
+ Held,
+ Hh,
+ Hjson,
+ Hlp,
+ Hpgl,
+ Hpid,
+ Hps,
+ Hqx,
+ Hsj2,
+ Htc,
+ Htke,
+ Htm,
+ Html,
+ Hvd,
+ Hvp,
+ Hvs,
+ I2g,
+ Icc,
+ Ice,
+ Icm,
+ Ico,
+ Ics,
+ Ief,
+ Ifb,
+ Ifm,
+ Iges,
+ Igl,
+ Igm,
+ Igs,
+ Igx,
+ Iif,
+ Img,
+ Imp,
+ Ims,
+ Ini,
+ Ink,
+ Inkml,
+ Install,
+ Iota,
+ Ipfix,
+ Ipk,
+ Irm,
+ Irp,
+ Iso,
+ Itp,
+ Its,
+ Ivp,
+ Ivu,
+ Jad,
+ Jade,
+ Jam,
+ Jar,
+ Jardiff,
+ Java,
+ Jhc,
+ Jisp,
+ Jls,
+ Jlt,
+ Jng,
+ Jnlp,
+ Joda,
+ Jp2,
+ Jpe,
+ Jpeg,
+ Jpf,
+ Jpg,
+ Jpg2,
+ Jpgm,
+ Jpgv,
+ Jph,
+ Jpm,
+ Jpx,
+ Javascript,
+ Json,
+ Json5,
+ Jsonld,
+ Jsonml,
+ Jsx,
+ Jxr,
+ Jxra,
+ Jxrs,
+ Jxs,
+ Jxsc,
+ Jxsi,
+ Jxss,
+ Kar,
+ Karbon,
+ Kdbx,
+ Key,
+ Kfo,
+ Kia,
+ Kml,
+ Kmz,
+ Kne,
+ Knp,
+ Kon,
+ Kpr,
+ Kpt,
+ Kpxx,
+ Ksp,
+ Ktr,
+ Ktx,
+ Ktx2,
+ Ktz,
+ Kwd,
+ Kwt,
+ Lasxml,
+ Latex,
+ Lbd,
+ Lbe,
+ Les,
+ Less,
+ Lgr,
+ Lha,
+ Link66,
+ List,
+ List3820,
+ Listafp,
+ Lnk,
+ Log,
+ Lostxml,
+ Lrf,
+ Lrm,
+ Ltf,
+ Lua,
+ Luac,
+ Lvp,
+ Lwp,
+ Lzh,
+ M13,
+ M14,
+ M1v,
+ M21,
+ M2a,
+ M2v,
+ M3a,
+ M3u,
+ M3u8,
+ M4a,
+ M4p,
+ M4s,
+ M4u,
+ M4v,
+ Ma,
+ Mads,
+ Maei,
+ Mag,
+ Maker,
+ Man,
+ Manifest,
+ Map,
+ Mar,
+ Markdown,
+ Mathml,
+ Mb,
+ Mbk,
+ Mbox,
+ Mc1,
+ Mcd,
+ Mcurl,
+ Md,
+ Mdb,
+ Mdi,
+ Mdx,
+ Me,
+ Mesh,
+ Meta4,
+ Metalink,
+ Mets,
+ Mfm,
+ Mft,
+ Mgp,
+ Mgz,
+ Mid,
+ Midi,
+ Mie,
+ Mif,
+ Mime,
+ Mj2,
+ Mjp2,
+ Mjs,
+ Mk3d,
+ Mka,
+ Mkd,
+ Mks,
+ Mkv,
+ Mlp,
+ Mmd,
+ Mmf,
+ Mml,
+ Mmr,
+ Mng,
+ Mny,
+ Mobi,
+ Mods,
+ Mov,
+ Movie,
+ Mp2,
+ Mp21,
+ Mp2a,
+ Mp3,
+ Mp4,
+ Mp4a,
+ Mp4s,
+ Mp4v,
+ Mpc,
+ Mpd,
+ Mpe,
+ Mpeg,
+ Mpg,
+ Mpg4,
+ Mpga,
+ Mpkg,
+ Mpm,
+ Mpn,
+ Mpp,
+ Mpt,
+ Mpy,
+ Mqy,
+ Mrc,
+ Mrcx,
+ Ms,
+ Mscml,
+ Mseed,
+ Mseq,
+ Msf,
+ Msg,
+ Msh,
+ Msi,
+ Msl,
+ Msm,
+ Msp,
+ Msty,
+ Mtl,
+ Mts,
+ Mus,
+ Musd,
+ Musicxml,
+ Mvb,
+ Mvt,
+ Mwf,
+ Mxf,
+ Mxl,
+ Mxmf,
+ Mxml,
+ Mxs,
+ Mxu,
+ N3,
+ Nb,
+ Nbp,
+ Nc,
+ Ncx,
+ Nfo,
+ Ngdat,
+ Nitf,
+ Nlu,
+ Nml,
+ Nnd,
+ Nns,
+ Nnw,
+ Npx,
+ Nq,
+ Nsc,
+ Nsf,
+ Nt,
+ Ntf,
+ Numbers,
+ Nzb,
+ Oa2,
+ Oa3,
+ Oas,
+ Obd,
+ Obgx,
+ Obj,
+ Oda,
+ Odb,
+ Odc,
+ Odf,
+ Odft,
+ Odg,
+ Odi,
+ Odm,
+ Odp,
+ Ods,
+ Odt,
+ Oga,
+ Ogex,
+ Ogg,
+ Ogv,
+ Ogx,
+ Omdoc,
+ Onepkg,
+ Onetmp,
+ Onetoc,
+ Onetoc2,
+ Opf,
+ Opml,
+ Oprc,
+ Opus,
+ Org,
+ Osf,
+ Osfpvg,
+ Osm,
+ Otc,
+ Otf,
+ Otg,
+ Oth,
+ Oti,
+ Otp,
+ Ots,
+ Ott,
+ Ova,
+ Ovf,
+ Owl,
+ Oxps,
+ Oxt,
+ P,
+ P10,
+ P12,
+ P7b,
+ P7c,
+ P7m,
+ P7r,
+ P7s,
+ P8,
+ Pac,
+ Pages,
+ Pas,
+ Paw,
+ Pbd,
+ Pbm,
+ Pcap,
+ Pcf,
+ Pcl,
+ Pclxl,
+ Pct,
+ Pcurl,
+ Pcx,
+ Pdb,
+ Pde,
+ Pdf,
+ Pem,
+ Pfa,
+ Pfb,
+ Pfm,
+ Pfr,
+ Pfx,
+ Pgm,
+ Pgn,
+ Pgp,
+ Php,
+ Pic,
+ Pkg,
+ Pki,
+ Pkipath,
+ Pkpass,
+ Pl,
+ Plb,
+ Plc,
+ Plf,
+ Pls,
+ Pm,
+ Pml,
+ Png,
+ Pnm,
+ Portpkg,
+ Pot,
+ Potm,
+ Potx,
+ Ppam,
+ Ppd,
+ Ppm,
+ Pps,
+ Ppsm,
+ Ppsx,
+ Ppt,
+ Pptm,
+ Pptx,
+ Pqa,
+ Prc,
+ Pre,
+ Prf,
+ Provx,
+ Ps,
+ Psb,
+ Psd,
+ Psf,
+ Pskcxml,
+ Pti,
+ Ptid,
+ Pub,
+ Pvb,
+ Pwn,
+ Pya,
+ Pyv,
+ Qam,
+ Qbo,
+ Qfx,
+ Qps,
+ Qt,
+ Qwd,
+ Qwt,
+ Qxb,
+ Qxd,
+ Qxl,
+ Qxt,
+ Ra,
+ Ram,
+ Raml,
+ Rapd,
+ Rar,
+ Ras,
+ Rdf,
+ Rdz,
+ Relo,
+ Rep,
+ Res,
+ Rgb,
+ Rif,
+ Rip,
+ Ris,
+ Rl,
+ Rlc,
+ Rld,
+ Rm,
+ Rmi,
+ Rmp,
+ Rms,
+ Rmvb,
+ Rnc,
+ Rng,
+ Roa,
+ Roff,
+ Rp9,
+ Rpm,
+ Rpss,
+ Rpst,
+ Rq,
+ Rs,
+ Rsat,
+ Rsd,
+ Rsheet,
+ Rss,
+ Rtf,
+ Rtx,
+ Run,
+ Rusd,
+ S,
+ S3m,
+ Saf,
+ Sass,
+ Sbml,
+ Sc,
+ Scd,
+ Scm,
+ Scq,
+ Scs,
+ Scss,
+ Scurl,
+ Sda,
+ Sdc,
+ Sdd,
+ Sdkd,
+ Sdkm,
+ Sdp,
+ Sdw,
+ Sea,
+ See,
+ Seed,
+ Sema,
+ Semd,
+ Semf,
+ Senmlx,
+ Sensmlx,
+ Ser,
+ Setpay,
+ Setreg,
+ Sfs,
+ Sfv,
+ Sgi,
+ Sgl,
+ Sgm,
+ Sgml,
+ Sh,
+ Shar,
+ Shex,
+ Shf,
+ Shtml,
+ Sid,
+ Sieve,
+ Sig,
+ Sil,
+ Silo,
+ Sis,
+ Sisx,
+ Sit,
+ Sitx,
+ Siv,
+ Skd,
+ Skm,
+ Skp,
+ Skt,
+ Sldm,
+ Sldx,
+ Slim,
+ Slm,
+ Sls,
+ Slt,
+ Sm,
+ Smf,
+ Smi,
+ Smil,
+ Smv,
+ Smzip,
+ Snd,
+ Snf,
+ So,
+ Spc,
+ Spdx,
+ Spf,
+ Spl,
+ Spot,
+ Spp,
+ Spq,
+ Spx,
+ Sql,
+ Src,
+ Srt,
+ Sru,
+ Srx,
+ Ssdl,
+ Sse,
+ Ssf,
+ Ssml,
+ St,
+ Stc,
+ Std,
+ Stf,
+ Sti,
+ Stk,
+ Stl,
+ Stpx,
+ Stpxz,
+ Stpz,
+ Str,
+ Stw,
+ Styl,
+ Stylus,
+ Sub,
+ Sus,
+ Susp,
+ Sv4cpio,
+ Sv4crc,
+ Svc,
+ Svd,
+ Svg,
+ Svgz,
+ Swa,
+ Swf,
+ Swi,
+ Swidtag,
+ Sxc,
+ Sxd,
+ Sxg,
+ Sxi,
+ Sxm,
+ Sxw,
+ T,
+ T3,
+ T38,
+ Taglet,
+ Tao,
+ Tap,
+ Tar,
+ Tcap,
+ Tcl,
+ Td,
+ Teacher,
+ Tei,
+ Tex,
+ Texi,
+ Texinfo,
+ Text,
+ Tfi,
+ Tfm,
+ Tfx,
+ Tga,
+ Thmx,
+ Tif,
+ Tiff,
+ Tk,
+ Tmo,
+ Toml,
+ Torrent,
+ Tpl,
+ Tpt,
+ Tr,
+ Tra,
+ Trig,
+ Trm,
+ Ts,
+ Tsd,
+ Tsv,
+ Ttc,
+ Ttf,
+ Ttl,
+ Ttml,
+ Twd,
+ Twds,
+ Txd,
+ Txf,
+ Txt,
+ U32,
+ U8dsn,
+ U8hdr,
+ U8mdn,
+ U8msg,
+ Ubj,
+ Udeb,
+ Ufd,
+ Ufdl,
+ Ulx,
+ Umj,
+ Unityweb,
+ Uoml,
+ Uri,
+ Uris,
+ Urls,
+ Usdz,
+ Ustar,
+ Utz,
+ Uu,
+ Uva,
+ Uvd,
+ Uvf,
+ Uvg,
+ Uvh,
+ Uvi,
+ Uvm,
+ Uvp,
+ Uvs,
+ Uvt,
+ Uvu,
+ Uvv,
+ Uvva,
+ Uvvd,
+ Uvvf,
+ Uvvg,
+ Uvvh,
+ Uvvi,
+ Uvvm,
+ Uvvp,
+ Uvvs,
+ Uvvt,
+ Uvvu,
+ Uvvv,
+ Uvvx,
+ Uvvz,
+ Uvx,
+ Uvz,
+ Vbox,
+ Vcard,
+ Vcd,
+ Vcf,
+ Vcg,
+ Vcs,
+ Vcx,
+ Vdi,
+ Vds,
+ Vhd,
+ Vis,
+ Viv,
+ Vmdk,
+ Vob,
+ Vor,
+ Vox,
+ Vrml,
+ Vsd,
+ Vsf,
+ Vss,
+ Vst,
+ Vsw,
+ Vtf,
+ Vtt,
+ Vtu,
+ Vxml,
+ W3d,
+ Wad,
+ Wadl,
+ War,
+ Wasm,
+ Wav,
+ Wax,
+ Wbmp,
+ Wbs,
+ Wbxml,
+ Wcm,
+ Wdb,
+ Wdp,
+ Weba,
+ Webapp,
+ Webm,
+ Webp,
+ Wg,
+ Wgt,
+ Wks,
+ Wm,
+ Wma,
+ Wmd,
+ Wmf,
+ Wml,
+ Wmlc,
+ Wmls,
+ Wmlsc,
+ Wmv,
+ Wmx,
+ Wmz,
+ Woff,
+ Woff2,
+ Wpd,
+ Wpl,
+ Wps,
+ Wqd,
+ Wri,
+ Wrl,
+ Wsc,
+ Wsdl,
+ Wspolicy,
+ Wtb,
+ Wvx,
+ X32,
+ X3d,
+ X3db,
+ X3dbz,
+ X3dv,
+ X3dvz,
+ X3dz,
+ Xaml,
+ Xap,
+ Xar,
+ Xav,
+ Xbap,
+ Xbd,
+ Xbm,
+ Xca,
+ Xcs,
+ Xdf,
+ Xdm,
+ Xdp,
+ Xdssc,
+ Xdw,
+ Xel,
+ Xenc,
+ Xer,
+ Xfdf,
+ Xfdl,
+ Xht,
+ Xhtml,
+ Xhvml,
+ Xif,
+ Xla,
+ Xlam,
+ Xlc,
+ Xlf,
+ Xlm,
+ Xls,
+ Xlsb,
+ Xlsm,
+ Xlsx,
+ Xlt,
+ Xltm,
+ Xltx,
+ Xlw,
+ Xm,
+ Xml,
+ Xns,
+ Xo,
+ Xop,
+ Xpi,
+ Xpl,
+ Xpm,
+ Xpr,
+ Xps,
+ Xpw,
+ Xpx,
+ Xsd,
+ Xsl,
+ Xslt,
+ Xsm,
+ Xspf,
+ Xul,
+ Xvm,
+ Xvml,
+ Xwd,
+ Xyz,
+ Xz,
+ Yaml,
+ Yang,
+ Yin,
+ Yml,
+ Ymp,
+ Z1,
+ Z2,
+ Z3,
+ Z4,
+ Z5,
+ Z6,
+ Z7,
+ Z8,
+ Zaz,
+ Zip,
+ Zir,
+ Zirz,
+ Zmm,
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Helpers/CoreBufferHelpers.cs b/Net.Http/src/Helpers/CoreBufferHelpers.cs
new file mode 100644
index 0000000..5c5d918
--- /dev/null
+++ b/Net.Http/src/Helpers/CoreBufferHelpers.cs
@@ -0,0 +1,188 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: CoreBufferHelpers.cs
+*
+* CoreBufferHelpers.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This class is meant to provide memory helper methods
+ * as a centralized HTTP local memory api.
+ *
+ * Pools and heaps are privatized to help avoid
+ * leaking sensitive HTTP data across other application
+ * allocations and help provide memory optimization.
+ */
+
+
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Security;
+using System.Threading;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+
+ /// <summary>
+ /// Provides memory pools and an internal heap for allocations.
+ /// </summary>
+ internal static class CoreBufferHelpers
+ {
+ private class InitDataBuffer : ISlindingWindowBuffer<byte>
+ {
+ private readonly ArrayPool<byte> pool;
+ private readonly int size;
+
+ private byte[]? buffer;
+
+ public InitDataBuffer(ArrayPool<byte> pool, int size)
+ {
+ this.buffer = pool.Rent(size, true);
+ this.pool = pool;
+ this.size = size;
+ WindowStartPos = 0;
+ WindowEndPos = 0;
+ }
+
+ public int WindowStartPos { get; set; }
+ public int WindowEndPos { get; set; }
+ Memory<byte> ISlindingWindowBuffer<byte>.Buffer => buffer.AsMemory(0, size);
+
+ public void Advance(int count)
+ {
+ WindowEndPos += count;
+ }
+
+ public void AdvanceStart(int count)
+ {
+ WindowStartPos += count;
+ }
+
+ public void Reset()
+ {
+ WindowStartPos = 0;
+ WindowEndPos = 0;
+ }
+
+ //Release the buffer back to the pool
+ void ISlindingWindowBuffer<byte>.Close()
+ {
+ pool.Return(buffer!);
+ buffer = null;
+ }
+ }
+
+ /// <summary>
+ /// An internal HTTP character binary pool for HTTP specific internal buffers
+ /// </summary>
+ public static ArrayPool<byte> HttpBinBufferPool { get; } = ArrayPool<byte>.Create();
+ /// <summary>
+ /// An <see cref="IUnmangedHeap"/> used for internal HTTP buffers
+ /// </summary>
+ public static IUnmangedHeap HttpPrivateHeap => _lazyHeap.Value;
+
+ private static readonly Lazy<IUnmangedHeap> _lazyHeap = new(Memory.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly);
+
+ /// <summary>
+ /// Alloctes an unsafe block of memory from the internal heap, or buffer pool
+ /// </summary>
+ /// <param name="size">The number of elemnts to allocate</param>
+ /// <param name="zero">A value indicating of the block should be zeroed before returning</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="SecurityException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static UnsafeMemoryHandle<byte> GetBinBuffer(int size, bool zero)
+ {
+ //Calc buffer size to the nearest page size
+ size = (size / 4096 + 1) * 4096;
+
+ //If rpmalloc lib is loaded, use it
+ if (Memory.IsRpMallocLoaded)
+ {
+ return Memory.Shared.UnsafeAlloc<byte>(size, zero);
+ }
+ else if (size > Memory.MAX_UNSAFE_POOL_SIZE)
+ {
+ return HttpPrivateHeap.UnsafeAlloc<byte>(size, zero);
+ }
+ else
+ {
+ return new(HttpBinBufferPool, size, zero);
+ }
+ }
+
+ public static IMemoryOwner<byte> GetMemory(int size, bool zero)
+ {
+ //Calc buffer size to the nearest page size
+ size = (size / 4096 + 1) * 4096;
+
+ //If rpmalloc lib is loaded, use it
+ if (Memory.IsRpMallocLoaded)
+ {
+ return Memory.Shared.DirectAlloc<byte>(size, zero);
+ }
+ //Avoid locking in heap unless the buffer is too large to alloc array
+ else if (size > Memory.MAX_UNSAFE_POOL_SIZE)
+ {
+ return HttpPrivateHeap.DirectAlloc<byte>(size, zero);
+ }
+ else
+ {
+ //Convert temp buffer to memory owner
+
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ return new VnTempBuffer<byte>(HttpBinBufferPool, size, zero).ToMemoryManager();
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ }
+ }
+
+ /// <summary>
+ /// Gets the remaining data in the reader buffer and prepares a
+ /// sliding window buffer to read data from
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="reader"></param>
+ /// <param name="maxContentLength">Maximum content size to clamp the remaining buffer window to</param>
+ /// <returns></returns>
+ public static ISlindingWindowBuffer<byte>? GetReminaingData<T>(this ref T reader, long maxContentLength) where T: struct, IVnTextReader
+ {
+ //clamp max available to max content length
+ int available = Math.Clamp(reader.Available, 0, (int)maxContentLength);
+ if (available <= 0)
+ {
+ return null;
+ }
+ //Alloc sliding window buffer
+ ISlindingWindowBuffer<byte> buffer = new InitDataBuffer(HttpBinBufferPool, available);
+ //Read remaining data
+ reader.ReadRemaining(buffer.RemainingBuffer.Span);
+ //Advance the buffer to the end of available data
+ buffer.Advance(available);
+ return buffer;
+ }
+
+ }
+}
diff --git a/Net.Http/src/Helpers/HelperTypes.cs b/Net.Http/src/Helpers/HelperTypes.cs
new file mode 100644
index 0000000..e89ca78
--- /dev/null
+++ b/Net.Http/src/Helpers/HelperTypes.cs
@@ -0,0 +1,98 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HelperTypes.cs
+*
+* HelperTypes.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Http
+{
+ [Flags]
+ public enum HttpMethod
+ {
+ NOT_SUPPORTED,
+ GET = 0x01,
+ POST = 0x02,
+ PUT = 0x04,
+ OPTIONS = 0x08,
+ HEAD = 0x10,
+ MERGE = 0x20,
+ COPY = 0x40,
+ DELETE = 0x80,
+ PATCH = 0x100,
+ TRACE = 0x200,
+ MOVE = 0x400,
+ LOCK = 0x800
+ }
+ /// <summary>
+ /// HTTP protocol version
+ /// </summary>
+ [Flags]
+ public enum HttpVersion
+ {
+ NotSupported,
+ Http1 = 0x01,
+ Http11 = 0x02,
+ Http2 = 0x04,
+ Http09 = 0x08
+ }
+ /// <summary>
+ /// HTTP response entity cache flags
+ /// </summary>
+ [Flags]
+ public enum CacheType
+ {
+ Ignore = 0x00,
+ NoCache = 0x01,
+ Private = 0x02,
+ Public = 0x04,
+ NoStore = 0x08,
+ Revalidate = 0x10
+ }
+
+ /// <summary>
+ /// Specifies an HTTP cookie SameSite type
+ /// </summary>
+ public enum CookieSameSite
+ {
+ Lax, None, SameSite
+ }
+
+ /// <summary>
+ /// Low level 301 "hard" redirect
+ /// </summary>
+ public class Redirect
+ {
+ public readonly string Url;
+ public readonly Uri RedirectUrl;
+ /// <summary>
+ /// Quickly redirects a url to another url before sessions are established
+ /// </summary>
+ /// <param name="url">Url to redirect on</param>
+ /// <param name="redirecturl">Url to redirect to</param>
+ public Redirect(string url, string redirecturl)
+ {
+ Url = url;
+ RedirectUrl = new(redirecturl);
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Helpers/HttpHelpers.cs b/Net.Http/src/Helpers/HttpHelpers.cs
new file mode 100644
index 0000000..9cceff1
--- /dev/null
+++ b/Net.Http/src/Helpers/HttpHelpers.cs
@@ -0,0 +1,445 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpHelpers.cs
+*
+* HttpHelpers.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+using VNLib.Net.Http.Core;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Provides a set of HTTP helper functions
+ /// </summary>
+ public static partial class HttpHelpers
+ {
+ /// <summary>
+ /// Carrage return + line feed characters used within the VNLib.Net.Http namespace to delimit http messages/lines
+ /// </summary>
+ public const string CRLF = "\r\n";
+
+ public const string WebsocketRFC4122Guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+ public const string EVENT_STREAM_ACCEPT_TYPE = "text/event-stream";
+
+ /// <summary>
+ /// Extended <see cref="HttpRequestHeader"/> for origin header, DO NOT USE IN <see cref="WebHeaderCollection"/>
+ /// </summary>
+ internal const HttpRequestHeader Origin = (HttpRequestHeader)42;
+ /// <summary>
+ /// Extended <see cref="HttpRequestHeader"/> for Content-Disposition, DO NOT USE IN <see cref="WebHeaderCollection"/>
+ /// </summary>
+ internal const HttpRequestHeader ContentDisposition = (HttpRequestHeader)41;
+
+ private static readonly Regex HttpRequestBuilderRegex = new("(?<=[a-z])([A-Z])", RegexOptions.Compiled);
+
+ /*
+ * Provides a hashable lookup table from a method string's hashcode to output
+ * an HttpMethod enum value,
+ */
+
+ private static readonly IReadOnlyDictionary<int, HttpMethod> MethodHashLookup;
+
+ /*
+ * Provides a constant lookup table from an MIME http request header string to a .NET
+ * enum value (with some extra support)
+ */
+
+ private static readonly IReadOnlyDictionary<string, HttpRequestHeader> RequestHeaderLookup = new Dictionary<string, HttpRequestHeader>()
+ {
+ {"CacheControl", HttpRequestHeader.CacheControl },
+ {"Connection", HttpRequestHeader.Connection },
+ {"Date", HttpRequestHeader.Date },
+ {"Keep-Alive", HttpRequestHeader.KeepAlive },
+ {"Pragma", HttpRequestHeader.Pragma },
+ {"Trailer", HttpRequestHeader.Trailer },
+ {"Transfer-Encoding", HttpRequestHeader.TransferEncoding },
+ {"Upgrade", HttpRequestHeader.Upgrade },
+ {"Via", HttpRequestHeader.Via },
+ {"Warning", HttpRequestHeader.Warning },
+ {"Allow", HttpRequestHeader.Allow },
+ {"Content-Length", HttpRequestHeader.ContentLength },
+ {"Content-Type", HttpRequestHeader.ContentType },
+ {"Content-Encoding", HttpRequestHeader.ContentEncoding },
+ {"Content-Language", HttpRequestHeader.ContentLanguage },
+ {"Content-Location", HttpRequestHeader.ContentLocation },
+ {"Content-Md5", HttpRequestHeader.ContentMd5 },
+ {"Content-Range", HttpRequestHeader.ContentRange },
+ {"Expires", HttpRequestHeader.Expires },
+ {"Last-Modified", HttpRequestHeader.LastModified },
+ {"Accept", HttpRequestHeader.Accept },
+ {"Accept-Charset", HttpRequestHeader.AcceptCharset },
+ {"Accept-Encoding", HttpRequestHeader.AcceptEncoding },
+ {"Accept-Language", HttpRequestHeader.AcceptLanguage },
+ {"Authorization", HttpRequestHeader.Authorization },
+ {"Cookie", HttpRequestHeader.Cookie },
+ {"Expect", HttpRequestHeader.Expect },
+ {"From", HttpRequestHeader.From },
+ {"Host", HttpRequestHeader.Host },
+ {"IfMatch", HttpRequestHeader.IfMatch },
+ {"If-Modified-Since", HttpRequestHeader.IfModifiedSince },
+ {"If-None-Match", HttpRequestHeader.IfNoneMatch },
+ {"If-Range", HttpRequestHeader.IfRange },
+ {"If-Unmodified-Since", HttpRequestHeader.IfUnmodifiedSince },
+ {"MaxForwards", HttpRequestHeader.MaxForwards },
+ {"Proxy-Authorization", HttpRequestHeader.ProxyAuthorization },
+ {"Referer", HttpRequestHeader.Referer },
+ {"Range", HttpRequestHeader.Range },
+ {"Te", HttpRequestHeader.Te },
+ {"Translate", HttpRequestHeader.Translate },
+ {"User-Agent", HttpRequestHeader.UserAgent },
+ //Custom request headers
+ { "Content-Disposition", ContentDisposition },
+ { "origin", Origin }
+ };
+
+ /*
+ * Provides a lookup table for request header hashcodes (that are hashed in
+ * the static constructor) to ouput an http request header enum value from
+ * a header string's hashcode (allows for spans to produce an enum value
+ * during request parsing)
+ *
+ */
+ private static readonly IReadOnlyDictionary<int, HttpRequestHeader> RequestHeaderHashLookup;
+
+ /*
+ * Provides a constant lookup table for http version hashcodes to an http
+ * version enum value
+ */
+ private static readonly IReadOnlyDictionary<int, HttpVersion> VersionHashLookup = new Dictionary<int, HttpVersion>()
+ {
+ { string.GetHashCode("HTTP/0.9", StringComparison.OrdinalIgnoreCase), HttpVersion.Http09 },
+ { string.GetHashCode("HTTP/1.0", StringComparison.OrdinalIgnoreCase), HttpVersion.Http1 },
+ { string.GetHashCode("HTTP/1.1", StringComparison.OrdinalIgnoreCase), HttpVersion.Http11 },
+ { string.GetHashCode("HTTP/2.0", StringComparison.OrdinalIgnoreCase), HttpVersion.Http2 }
+ };
+
+
+ //Pre-compiled strings for all status codes for http 1, 1.1, and 2
+ private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_STAUTS_CODES;
+ private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_1_STATUS_CODES;
+ private static readonly IReadOnlyDictionary<HttpStatusCode, string> V2_STAUTS_CODES;
+
+ static HttpHelpers()
+ {
+ {
+ //Setup status code dict
+ Dictionary<HttpStatusCode, string> v1status = new();
+ Dictionary<HttpStatusCode, string> v11status = new();
+ Dictionary<HttpStatusCode, string> v2status = new();
+ //Get all status codes
+ foreach (HttpStatusCode code in Enum.GetValues<HttpStatusCode>())
+ {
+ //Use a regex to write the status code value as a string
+ v1status[code] = $"HTTP/1.0 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}";
+ v11status[code] = $"HTTP/1.1 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}";
+ v2status[code] = $"HTTP/2.0 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}";
+ }
+ //Store as readonly
+ V1_STAUTS_CODES = v1status;
+ V1_1_STATUS_CODES = v11status;
+ V2_STAUTS_CODES = v2status;
+ }
+ {
+ /*
+ * Http methods are hashed at runtime using the HttpMethod enum
+ * values, purley for compatability and automation
+ */
+ Dictionary<int, HttpMethod> methods = new();
+ //Add all HTTP methods
+ foreach (HttpMethod method in Enum.GetValues<HttpMethod>())
+ {
+ //Exclude the not supported method
+ if (method == HttpMethod.NOT_SUPPORTED)
+ {
+ continue;
+ }
+ //Store method string's hashcode for faster lookups
+ methods[string.GetHashCode(method.ToString(), StringComparison.OrdinalIgnoreCase)] = method;
+ }
+ MethodHashLookup = methods;
+ }
+ {
+ /*
+ * Pre-compute common headers
+ */
+ Dictionary<int, HttpRequestHeader> requestHeaderHashes = new();
+ //Add all HTTP methods
+ foreach (string headerValue in RequestHeaderLookup.Keys)
+ {
+ //Compute the hashcode for the header value
+ int hashCode = string.GetHashCode(headerValue, StringComparison.OrdinalIgnoreCase);
+ //Store the http header enum value with the hash-code of the string of said header
+ requestHeaderHashes[hashCode] = RequestHeaderLookup[headerValue];
+ }
+ RequestHeaderHashLookup = requestHeaderHashes;
+ }
+ }
+
+
+ /// <summary>
+ /// Returns an http formatted content type string of a specified content type
+ /// </summary>
+ /// <param name="type">Contenty type</param>
+ /// <returns>Http acceptable string representing a content type</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ public static string GetContentTypeString(ContentType type) => CtToMime[type];
+ /// <summary>
+ /// Returns the <see cref="ContentType"/> enum value from the MIME string
+ /// </summary>
+ /// <param name="type">Content type from request</param>
+ /// <returns><see cref="ContentType"/> of request, <see cref="ContentType.NonSupported"/> if unknown</returns>
+ public static ContentType GetContentType(string type) => MimeToCt.GetValueOrDefault(type, ContentType.NonSupported);
+ //Cache control string using mdn reference
+ //https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
+ /// <summary>
+ /// Builds a Cache-Control MIME content header from the specified flags
+ /// </summary>
+ /// <param name="type">The cache type/mode</param>
+ /// <param name="maxAge">The max-age (time in seconds) argument</param>
+ /// <param name="immutable">Sets the immutable argument</param>
+ /// <returns>The string representation of the Cache-Control header</returns>
+ public static string GetCacheString(CacheType type, int maxAge = 0, bool immutable = false)
+ {
+ //Rent a buffer to write header to
+ Span<char> buffer = stackalloc char[128];
+ //Get buffer writer for cache header
+ ForwardOnlyWriter<char> sb = new(buffer);
+ if ((type & CacheType.NoCache) > 0)
+ {
+ sb.Append("no-cache, ");
+ }
+ if ((type & CacheType.NoStore) > 0)
+ {
+ sb.Append("no-store, ");
+ }
+ if ((type & CacheType.Public) > 0)
+ {
+ sb.Append("public, ");
+ }
+ if ((type & CacheType.Private) > 0)
+ {
+ sb.Append("private, ");
+ }
+ if ((type & CacheType.Revalidate) > 0)
+ {
+ sb.Append("must-revalidate, ");
+ }
+ if (immutable)
+ {
+ sb.Append("immutable, ");
+ }
+ sb.Append("max-age=");
+ sb.Append(maxAge);
+ return sb.ToString();
+ }
+ /// <summary>
+ /// Builds a Cache-Control MIME content header from the specified flags
+ /// </summary>
+ /// <param name="type">The cache type/mode</param>
+ /// <param name="maxAge">The max-age argument</param>
+ /// <param name="immutable">Sets the immutable argument</param>
+ /// <returns>The string representation of the Cache-Control header</returns>
+ public static string GetCacheString(CacheType type, TimeSpan maxAge, bool immutable = false) => GetCacheString(type, (int)maxAge.TotalSeconds, immutable);
+ /// <summary>
+ /// Returns an enum value of an httpmethod of an http request method string
+ /// </summary>
+ /// <param name="smethod">Http acceptable method type string</param>
+ /// <returns>Request method, <see cref="HttpMethod.NOT_SUPPORTED"/> if method is malformatted or unsupported</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static HttpMethod GetRequestMethod(ReadOnlySpan<char> smethod)
+ {
+ //Get the hashcode for the method "string"
+ int hashCode = string.GetHashCode(smethod, StringComparison.OrdinalIgnoreCase);
+ //run the lookup and return not supported if the method was not found
+ return MethodHashLookup.GetValueOrDefault(hashCode, HttpMethod.NOT_SUPPORTED);
+ }
+ /// <summary>
+ /// Compares the first 3 bytes of IPV4 ip address or the first 6 bytes of a IPV6. Can be used to determine if the address is local to another address
+ /// </summary>
+ /// <param name="first">Address to be compared</param>
+ /// <param name="other">Address to be comared to first address</param>
+ /// <returns>True if first 2 bytes of each address match (Big Endian)</returns>
+ public static bool IsLocalSubnet(this IPAddress first, IPAddress other)
+ {
+ if(first.AddressFamily != other.AddressFamily)
+ {
+ return false;
+ }
+ switch (first.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ {
+ //Alloc buffers 4 bytes for IPV4
+ Span<byte> firstBytes = stackalloc byte[4];
+ Span<byte> otherBytes = stackalloc byte[4];
+ //Write address's to the buffers
+ if (first.TryWriteBytes(firstBytes, out _) && other.TryWriteBytes(otherBytes, out _))
+ {
+ //Compare the first 3 bytes of the first address to the second address
+ return firstBytes.StartsWith(otherBytes[..3]);
+ }
+ }
+ break;
+ case AddressFamily.InterNetworkV6:
+ {
+ //Alloc buffers 8 bytes for IPV6
+ Span<byte> firstBytes = stackalloc byte[8];
+ Span<byte> otherBytes = stackalloc byte[8];
+ //Write address's to the buffers
+ if (first.TryWriteBytes(firstBytes, out _) && other.TryWriteBytes(otherBytes, out _))
+ {
+ //Compare the first 6 bytes of the first address to the second address
+ return firstBytes.StartsWith(otherBytes[..6]);
+ }
+ }
+ break;
+ }
+ return false;
+ }
+ /// <summary>
+ /// Selects a <see cref="ContentType"/> for a given file extension
+ /// </summary>
+ /// <param name="path">Path (including extension) of a file</param>
+ /// <returns><see cref="ContentType"/> of file. Returns <see cref="ContentType.Binary"/> if extension is unknown</returns>
+ public static ContentType GetContentTypeFromFile(ReadOnlySpan<char> path)
+ {
+ //Get the file's extension
+ ReadOnlySpan<char> extention = Path.GetExtension(path);
+ //Trim leading .
+ extention = extention.Trim('.');
+ //If the extension is defined, perform a lookup, otherwise return the default
+ return ExtensionToCt.GetValueOrDefault(extention.ToString(), ContentType.Binary);
+ }
+ /// <summary>
+ /// Selects a runtime compiled <see cref="string"/> matching the given <see cref="HttpStatusCode"/> and <see cref="HttpVersion"/>
+ /// </summary>
+ /// <param name="version">Version of the response string</param>
+ /// <param name="code">Status code of the response</param>
+ /// <returns>The HTTP response status line matching the code and version</returns>
+ public static string GetResponseString(HttpVersion version, HttpStatusCode code)
+ {
+ return version switch
+ {
+ HttpVersion.Http1 => V1_STAUTS_CODES[code],
+ HttpVersion.Http2 => V2_STAUTS_CODES[code],
+ _ => V1_1_STATUS_CODES[code],
+ };
+ }
+
+ /// <summary>
+ /// Parses the mime Content-Type header value into its sub-components
+ /// </summary>
+ /// <param name="header">The Content-Type header value field</param>
+ /// <param name="ContentType">The mime content type field</param>
+ /// <param name="Charset">The mime charset</param>
+ /// <param name="Boundry">The multi-part form boundry parameter</param>
+ /// <returns>True if parsing the content type succeded, false otherwise</returns>
+ public static bool TryParseContentType(string header, out string? ContentType, out string? Charset, out string? Boundry)
+ {
+ try
+ {
+ //Parse content type
+ System.Net.Mime.ContentType ctype = new(header);
+ Boundry = ctype.Boundary;
+ Charset = ctype.CharSet;
+ ContentType = ctype.MediaType;
+ return true;
+ }
+ catch
+//Disable warning for not using the exception, intended behavior
+#pragma warning disable ERP022 // Unobserved exception in a generic exception handler.
+ {
+ ContentType = Charset = Boundry = null;
+ //Invalid content type header value
+ }
+#pragma warning restore ERP022 // Unobserved exception in a generic exception handler.
+ return false;
+ }
+
+ /// <summary>
+ /// Parses a standard HTTP Content disposition header into its sub-components, type, name, filename (optional)
+ /// </summary>
+ /// <param name="header">The buffer containing the Content-Disposition header value only</param>
+ /// <param name="type">The mime form type</param>
+ /// <param name="name">The mime name argument</param>
+ /// <param name="fileName">The mime filename</param>
+ public static void ParseDisposition(ReadOnlySpan<char> header, out string? type, out string? name, out string? fileName)
+ {
+ //First parameter should be the type argument
+ type = header.SliceBeforeParam(';').Trim().ToString();
+ //Set defaults for name and filename
+ name = fileName = null;
+ //get the name parameter
+ ReadOnlySpan<char> nameSpan = header.SliceAfterParam("name=\"");
+ if (!nameSpan.IsEmpty)
+ {
+ //Capture the name parameter value and trim it up
+ name = nameSpan.SliceBeforeParam('"').Trim().ToString();
+ }
+ //Check for the filename parameter
+ ReadOnlySpan<char> fileNameSpan = header.SliceAfterParam("filename=\"");
+ if (!fileNameSpan.IsEmpty)
+ {
+ //Capture the name parameter value and trim it up
+ fileName = fileNameSpan.SliceBeforeParam('"').Trim().ToString();
+ }
+ }
+
+ /// <summary>
+ /// Performs a lookup of the specified header name to get the <see cref="HttpRequestHeader"/> enum value
+ /// </summary>
+ /// <param name="requestHeaderName">The value of the HTTP request header to compute</param>
+ /// <returns>The <see cref="HttpRequestHeader"/> enum value of the header, or 255 if not found</returns>
+ internal static HttpRequestHeader GetRequestHeaderEnumFromValue(ReadOnlySpan<char> requestHeaderName)
+ {
+ //Compute the hashcode from the header name
+ int hashcode = string.GetHashCode(requestHeaderName, StringComparison.OrdinalIgnoreCase);
+ //perform lookup
+ return RequestHeaderHashLookup.GetValueOrDefault(hashcode, (HttpRequestHeader)255);
+ }
+
+ /// <summary>
+ /// Gets the <see cref="HttpVersion"/> enum value from the version string
+ /// </summary>
+ /// <param name="httpVersion">The http header version string</param>
+ /// <returns>The <see cref="HttpVersion"/> enum value, or
+ /// <see cref="HttpVersion.NotSupported"/> if the version could not be
+ /// determined
+ /// </returns>
+ public static HttpVersion ParseHttpVersion(ReadOnlySpan<char> httpVersion)
+ {
+ //Get the hashcode for the http version "string"
+ int hashCode = string.GetHashCode(httpVersion.Trim(), StringComparison.OrdinalIgnoreCase);
+ //return the version that matches the hashcode, or return unsupported of not found
+ return VersionHashLookup.GetValueOrDefault(hashCode, HttpVersion.NotSupported);
+ }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/Helpers/MimeLookups.cs b/Net.Http/src/Helpers/MimeLookups.cs
new file mode 100644
index 0000000..03bc59d
--- /dev/null
+++ b/Net.Http/src/Helpers/MimeLookups.cs
@@ -0,0 +1,3237 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: MimeLookups.cs
+*
+* MimeLookups.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ public static partial class HttpHelpers
+ {
+ //Content type lookup dict
+ private static readonly IReadOnlyDictionary<ContentType, string> CtToMime = new Dictionary<ContentType, string>()
+ {
+ { ContentType.NonSupported, "application/octet-stream" },
+ { ContentType.UrlEncoded, "application/x-www-form-urlencoded" },
+ { ContentType.MultiPart, "multipart/form-data" },
+ { ContentType.Aab, "application/x-authorware-bin" },
+ { ContentType.Aac, "audio/x-aac" },
+ { ContentType.Aam, "application/x-authorware-map" },
+ { ContentType.Aas, "application/x-authorware-seg" },
+ { ContentType.Abw, "application/x-abiword" },
+ { ContentType.Ac, "application/pkix-attr-cert" },
+ { ContentType.Acc, "application/vnd.americandynamics.acc" },
+ { ContentType.Ace, "application/x-ace-compressed" },
+ { ContentType.Acu, "application/vnd.acucobol" },
+ { ContentType.Acutc, "application/vnd.acucorp" },
+ { ContentType.Adp, "audio/adpcm" },
+ { ContentType.Aep, "application/vnd.audiograph" },
+ { ContentType.Afm, "application/x-font-type1" },
+ { ContentType.Afp, "application/vnd.ibm.modcap" },
+ { ContentType.Ahead, "application/vnd.ahead.space" },
+ { ContentType.Ai, "application/postscript" },
+ { ContentType.Aif, "audio/x-aiff" },
+ { ContentType.Aifc, "audio/x-aiff" },
+ { ContentType.Aiff, "audio/x-aiff" },
+ { ContentType.Air, "application/vnd.adobe.air-application-installer-package+zip" },
+ { ContentType.Ait, "application/vnd.dvb.ait" },
+ { ContentType.Ami, "application/vnd.amiga.ami" },
+ { ContentType.Amr, "audio/amr" },
+ { ContentType.Apk, "application/vnd.android.package-archive" },
+ { ContentType.Apng, "image/apng" },
+ { ContentType.Appcache, "text/cache-manifest" },
+ { ContentType.Apr, "application/vnd.lotus-approach" },
+ { ContentType.Arc, "application/x-freearc" },
+ { ContentType.Arj, "application/x-arj" },
+ { ContentType.Asc, "application/pgp-signature" },
+ { ContentType.Asf, "video/x-ms-asf" },
+ { ContentType.Asm, "text/x-asm" },
+ { ContentType.Aso, "application/vnd.accpac.simply.aso" },
+ { ContentType.Asx, "video/x-ms-asf" },
+ { ContentType.Atc, "application/vnd.acucorp" },
+ { ContentType.Atom, "application/atom+xml" },
+ { ContentType.Atomcat, "application/atomcat+xml" },
+ { ContentType.Atomsvc, "application/atomsvc+xml" },
+ { ContentType.Atx, "application/vnd.antix.game-component" },
+ { ContentType.Au, "audio/basic" },
+ { ContentType.Avi, "video/x-msvideo" },
+ { ContentType.Avif, "image/avif" },
+ { ContentType.Aw, "application/applixware" },
+ { ContentType.Azf, "application/vnd.airzip.filesecure.azf" },
+ { ContentType.Azs, "application/vnd.airzip.filesecure.azs" },
+ { ContentType.Azv, "image/vnd.airzip.accelerator.azv" },
+ { ContentType.Azw, "application/vnd.amazon.ebook" },
+ { ContentType.B16, "image/vnd.pco.b16" },
+ { ContentType.Bat, "application/x-msdownload" },
+ { ContentType.Bcpio, "application/x-bcpio" },
+ { ContentType.Bdf, "application/x-font-bdf" },
+ { ContentType.Bdm, "application/vnd.syncml.dm+wbxml" },
+ { ContentType.Bdoc, "application/bdoc" },
+ { ContentType.Bed, "application/vnd.realvnc.bed" },
+ { ContentType.Bh2, "application/vnd.fujitsu.oasysprs" },
+ { ContentType.Binary, "application/octet-stream" },
+ { ContentType.Blb, "application/x-blorb" },
+ { ContentType.Blorb, "application/x-blorb" },
+ { ContentType.Bmi, "application/vnd.bmi" },
+ { ContentType.Bmml, "application/vnd.balsamiq.bmml+xml" },
+ { ContentType.Bmp, "image/bmp" },
+ { ContentType.Book, "application/vnd.framemaker" },
+ { ContentType.Box, "application/vnd.previewsystems.box" },
+ { ContentType.Boz, "application/x-bzip2" },
+ { ContentType.Bpk, "application/octet-stream" },
+ { ContentType.Bsp, "model/vnd.valve.source.compiled-map" },
+ { ContentType.Btif, "image/prs.btif" },
+ { ContentType.Buffer, "application/octet-stream" },
+ { ContentType.Bz, "application/x-bzip" },
+ { ContentType.Bz2, "application/x-bzip2" },
+ { ContentType.C, "text/x-c" },
+ { ContentType.C11amc, "application/vnd.cluetrust.cartomobile-config" },
+ { ContentType.C11amz, "application/vnd.cluetrust.cartomobile-config-pkg" },
+ { ContentType.C4d, "application/vnd.clonk.c4group" },
+ { ContentType.C4f, "application/vnd.clonk.c4group" },
+ { ContentType.C4g, "application/vnd.clonk.c4group" },
+ { ContentType.C4p, "application/vnd.clonk.c4group" },
+ { ContentType.C4u, "application/vnd.clonk.c4group" },
+ { ContentType.Cab, "application/vnd.ms-cab-compressed" },
+ { ContentType.Caf, "audio/x-caf" },
+ { ContentType.Cap, "application/vnd.tcpdump.pcap" },
+ { ContentType.Car, "application/vnd.curl.car" },
+ { ContentType.Cat, "application/vnd.ms-pki.seccat" },
+ { ContentType.Cb7, "application/x-cbr" },
+ { ContentType.Cba, "application/x-cbr" },
+ { ContentType.Cbr, "application/x-cbr" },
+ { ContentType.Cbt, "application/x-cbr" },
+ { ContentType.Cbz, "application/x-cbr" },
+ { ContentType.Cc, "text/x-c" },
+ { ContentType.Cco, "application/x-cocoa" },
+ { ContentType.Cct, "application/x-director" },
+ { ContentType.Ccxml, "application/ccxml+xml" },
+ { ContentType.Cdbcmsg, "application/vnd.contact.cmsg" },
+ { ContentType.Cdf, "application/x-netcdf" },
+ { ContentType.Cdfx, "application/cdfx+xml" },
+ { ContentType.Cdkey, "application/vnd.mediastation.cdkey" },
+ { ContentType.Cdmia, "application/cdmi-capability" },
+ { ContentType.Cdmic, "application/cdmi-container" },
+ { ContentType.Cdmid, "application/cdmi-domain" },
+ { ContentType.Cdmio, "application/cdmi-object" },
+ { ContentType.Cdmiq, "application/cdmi-queue" },
+ { ContentType.Cdx, "chemical/x-cdx" },
+ { ContentType.Cdxml, "application/vnd.chemdraw+xml" },
+ { ContentType.Cdy, "application/vnd.cinderella" },
+ { ContentType.Cer, "application/pkix-cert" },
+ { ContentType.Cfs, "application/x-cfs-compressed" },
+ { ContentType.Cgm, "image/cgm" },
+ { ContentType.Chat, "application/x-chat" },
+ { ContentType.Chm, "application/vnd.ms-htmlhelp" },
+ { ContentType.Chrt, "application/vnd.kde.kchart" },
+ { ContentType.Cif, "chemical/x-cif" },
+ { ContentType.Cii, "application/vnd.anser-web-certificate-issue-initiation" },
+ { ContentType.Cil, "application/vnd.ms-artgalry" },
+ { ContentType.Cjs, "application/node" },
+ { ContentType.Cla, "application/vnd.claymore" },
+ { ContentType.Clkk, "application/vnd.crick.clicker.keyboard" },
+ { ContentType.Clkp, "application/vnd.crick.clicker.palette" },
+ { ContentType.Clkt, "application/vnd.crick.clicker.template" },
+ { ContentType.Clkw, "application/vnd.crick.clicker.wordbank" },
+ { ContentType.Clkx, "application/vnd.crick.clicker" },
+ { ContentType.Clp, "application/x-msclip" },
+ { ContentType.Cmc, "application/vnd.cosmocaller" },
+ { ContentType.Cmdf, "chemical/x-cmdf" },
+ { ContentType.Cml, "chemical/x-cml" },
+ { ContentType.Cmp, "application/vnd.yellowriver-custom-menu" },
+ { ContentType.Cmx, "image/x-cmx" },
+ { ContentType.Cod, "application/vnd.rim.cod" },
+ { ContentType.Coffee, "text/coffeescript" },
+ { ContentType.Com, "application/x-msdownload" },
+ { ContentType.Conf, "text/plain" },
+ { ContentType.Cpio, "application/x-cpio" },
+ { ContentType.Cpp, "text/x-c" },
+ { ContentType.Cpt, "application/mac-compactpro" },
+ { ContentType.Crd, "application/x-mscardfile" },
+ { ContentType.Crl, "application/pkix-crl" },
+ { ContentType.Crt, "application/x-x509-ca-cert" },
+ { ContentType.Crx, "application/x-chrome-extension" },
+ { ContentType.Csh, "application/x-csh" },
+ { ContentType.Csl, "application/vnd.citationstyles.style+xml" },
+ { ContentType.Csml, "chemical/x-csml" },
+ { ContentType.Csp, "application/vnd.commonspace" },
+ { ContentType.Css, "text/css" },
+ { ContentType.Cst, "application/x-director" },
+ { ContentType.Csv, "text/csv" },
+ { ContentType.Cu, "application/cu-seeme" },
+ { ContentType.Curl, "text/vnd.curl" },
+ { ContentType.Cww, "application/prs.cww" },
+ { ContentType.Cxt, "application/x-director" },
+ { ContentType.Cxx, "text/x-c" },
+ { ContentType.Dae, "model/vnd.collada+xml" },
+ { ContentType.Daf, "application/vnd.mobius.daf" },
+ { ContentType.Dart, "application/vnd.dart" },
+ { ContentType.Dataless, "application/vnd.fdsn.seed" },
+ { ContentType.Davmount, "application/davmount+xml" },
+ { ContentType.Dbf, "application/vnd.dbf" },
+ { ContentType.Dbk, "application/docbook+xml" },
+ { ContentType.Dcr, "application/x-director" },
+ { ContentType.Dcurl, "text/vnd.curl.dcurl" },
+ { ContentType.Dd2, "application/vnd.oma.dd2+xml" },
+ { ContentType.Ddd, "application/vnd.fujixerox.ddd" },
+ { ContentType.Ddf, "application/vnd.syncml.dmddf+xml" },
+ { ContentType.Dds, "image/vnd.ms-dds" },
+ { ContentType.Deb, "application/octet-stream" },
+ { ContentType.Def, "text/plain" },
+ { ContentType.Deploy, "application/octet-stream" },
+ { ContentType.Der, "application/x-x509-ca-cert" },
+ { ContentType.Dfac, "application/vnd.dreamfactory" },
+ { ContentType.Dgc, "application/x-dgc-compressed" },
+ { ContentType.Dic, "text/x-c" },
+ { ContentType.Dir, "application/x-director" },
+ { ContentType.Dis, "application/vnd.mobius.dis" },
+ { ContentType.Dist, "application/octet-stream" },
+ { ContentType.Distz, "application/octet-stream" },
+ { ContentType.Djv, "image/vnd.djvu" },
+ { ContentType.Djvu, "image/vnd.djvu" },
+ { ContentType.Dll, "application/octet-stream" },
+ { ContentType.Dmg, "application/octet-stream" },
+ { ContentType.Dmp, "application/vnd.tcpdump.pcap" },
+ { ContentType.Dms, "application/octet-stream" },
+ { ContentType.Dna, "application/vnd.dna" },
+ { ContentType.Doc, "application/msword" },
+ { ContentType.Docm, "application/vnd.ms-word.document.macroenabled.12" },
+ { ContentType.Docx, "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
+ { ContentType.Dot, "application/msword" },
+ { ContentType.Dotm, "application/vnd.ms-word.template.macroenabled.12" },
+ { ContentType.Dotx, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
+ { ContentType.Dp, "application/vnd.osgi.dp" },
+ { ContentType.Dpg, "application/vnd.dpgraph" },
+ { ContentType.Dra, "audio/vnd.dra" },
+ { ContentType.Drle, "image/dicom-rle" },
+ { ContentType.Dsc, "text/prs.lines.tag" },
+ { ContentType.Dssc, "application/dssc+der" },
+ { ContentType.Dtb, "application/x-dtbook+xml" },
+ { ContentType.Dtd, "application/xml-dtd" },
+ { ContentType.Dts, "audio/vnd.dts" },
+ { ContentType.Dtshd, "audio/vnd.dts.hd" },
+ { ContentType.Dump, "application/octet-stream" },
+ { ContentType.Dvb, "video/vnd.dvb.file" },
+ { ContentType.Dvi, "application/x-dvi" },
+ { ContentType.Dwd, "application/atsc-dwd+xml" },
+ { ContentType.Dwf, "model/vnd.dwf" },
+ { ContentType.Dwg, "image/vnd.dwg" },
+ { ContentType.Dxf, "image/vnd.dxf" },
+ { ContentType.Dxp, "application/vnd.spotfire.dxp" },
+ { ContentType.Dxr, "application/x-director" },
+ { ContentType.Ear, "application/java-archive" },
+ { ContentType.Ecma, "application/ecmascript" },
+ { ContentType.Edm, "application/vnd.novadigm.edm" },
+ { ContentType.Edx, "application/vnd.novadigm.edx" },
+ { ContentType.Efif, "application/vnd.picsel" },
+ { ContentType.Ei6, "application/vnd.pg.osasli" },
+ { ContentType.Elc, "application/octet-stream" },
+ { ContentType.Emf, "application/x-msmetafile" },
+ { ContentType.Eml, "message/rfc822" },
+ { ContentType.Emma, "application/emma+xml" },
+ { ContentType.Emz, "application/x-msmetafile" },
+ { ContentType.Eol, "audio/vnd.digital-winds" },
+ { ContentType.Eot, "application/vnd.ms-fontobject" },
+ { ContentType.Eps, "application/postscript" },
+ { ContentType.Epub, "application/epub+zip" },
+ { ContentType.Es, "application/ecmascript" },
+ { ContentType.Es3, "application/vnd.eszigno3+xml" },
+ { ContentType.Esa, "application/vnd.osgi.subsystem" },
+ { ContentType.Esf, "application/vnd.epson.esf" },
+ { ContentType.Et3, "application/vnd.eszigno3+xml" },
+ { ContentType.Etx, "text/x-setext" },
+ { ContentType.Eva, "application/x-eva" },
+ { ContentType.Evy, "application/x-envoy" },
+ { ContentType.Exe, "application/octet-stream" },
+ { ContentType.Exi, "application/exi" },
+ { ContentType.Exp, "application/express" },
+ { ContentType.Exr, "image/aces" },
+ { ContentType.Ext, "application/vnd.novadigm.ext" },
+ { ContentType.Ez, "application/andrew-inset" },
+ { ContentType.Ez2, "application/vnd.ezpix-album" },
+ { ContentType.Ez3, "application/vnd.ezpix-package" },
+ { ContentType.F, "text/x-fortran" },
+ { ContentType.F4v, "video/x-f4v" },
+ { ContentType.Fortran, "text/x-fortran" },
+ { ContentType.F90, "text/x-fortran" },
+ { ContentType.Fbs, "image/vnd.fastbidsheet" },
+ { ContentType.Fcdt, "application/vnd.adobe.formscentral.fcdt" },
+ { ContentType.Fcs, "application/vnd.isac.fcs" },
+ { ContentType.Fdf, "application/vnd.fdf" },
+ { ContentType.Fdt, "application/fdt+xml" },
+ { ContentType.Fg5, "application/vnd.fujitsu.oasysgp" },
+ { ContentType.Fgd, "application/x-director" },
+ { ContentType.Fh, "image/x-freehand" },
+ { ContentType.Fh4, "image/x-freehand" },
+ { ContentType.Fh5, "image/x-freehand" },
+ { ContentType.Fh7, "image/x-freehand" },
+ { ContentType.Fhc, "image/x-freehand" },
+ { ContentType.Fig, "application/x-xfig" },
+ { ContentType.Fits, "image/fits" },
+ { ContentType.Flac, "audio/x-flac" },
+ { ContentType.Fli, "video/x-fli" },
+ { ContentType.Flo, "application/vnd.micrografx.flo" },
+ { ContentType.Flv, "video/x-flv" },
+ { ContentType.Flw, "application/vnd.kde.kivio" },
+ { ContentType.Flx, "text/vnd.fmi.flexstor" },
+ { ContentType.Fly, "text/vnd.fly" },
+ { ContentType.Fm, "application/vnd.framemaker" },
+ { ContentType.Fnc, "application/vnd.frogans.fnc" },
+ { ContentType.Fo, "application/vnd.software602.filler.form+xml" },
+ { ContentType.For, "text/x-fortran" },
+ { ContentType.Fpx, "image/vnd.fpx" },
+ { ContentType.Frame, "application/vnd.framemaker" },
+ { ContentType.Fsc, "application/vnd.fsc.weblaunch" },
+ { ContentType.Fst, "image/vnd.fst" },
+ { ContentType.Ftc, "application/vnd.fluxtime.clip" },
+ { ContentType.Fti, "application/vnd.anser-web-funds-transfer-initiation" },
+ { ContentType.Fvt, "video/vnd.fvt" },
+ { ContentType.Fxp, "application/vnd.adobe.fxp" },
+ { ContentType.Fxpl, "application/vnd.adobe.fxp" },
+ { ContentType.Fzs, "application/vnd.fuzzysheet" },
+ { ContentType.G2w, "application/vnd.geoplan" },
+ { ContentType.G3, "image/g3fax" },
+ { ContentType.G3w, "application/vnd.geospace" },
+ { ContentType.Gac, "application/vnd.groove-account" },
+ { ContentType.Gam, "application/x-tads" },
+ { ContentType.Gbr, "application/rpki-ghostbusters" },
+ { ContentType.Gca, "application/x-gca-compressed" },
+ { ContentType.Gdl, "model/vnd.gdl" },
+ { ContentType.Gdoc, "application/vnd.google-apps.document" },
+ { ContentType.Geo, "application/vnd.dynageo" },
+ { ContentType.Geojson, "application/geo+json" },
+ { ContentType.Gex, "application/vnd.geometry-explorer" },
+ { ContentType.Ggb, "application/vnd.geogebra.file" },
+ { ContentType.Ggt, "application/vnd.geogebra.tool" },
+ { ContentType.Ghf, "application/vnd.groove-help" },
+ { ContentType.Gif, "image/gif" },
+ { ContentType.Gim, "application/vnd.groove-identity-message" },
+ { ContentType.Glb, "model/gltf-binary" },
+ { ContentType.Gltf, "model/gltf+json" },
+ { ContentType.Gml, "application/gml+xml" },
+ { ContentType.Gmx, "application/vnd.gmx" },
+ { ContentType.Gnumeric, "application/x-gnumeric" },
+ { ContentType.Gph, "application/vnd.flographit" },
+ { ContentType.Gpx, "application/gpx+xml" },
+ { ContentType.Gqf, "application/vnd.grafeq" },
+ { ContentType.Gqs, "application/vnd.grafeq" },
+ { ContentType.Gram, "application/srgs" },
+ { ContentType.Gramps, "application/x-gramps-xml" },
+ { ContentType.Gre, "application/vnd.geometry-explorer" },
+ { ContentType.Grv, "application/vnd.groove-injector" },
+ { ContentType.Grxml, "application/srgs+xml" },
+ { ContentType.Gsf, "application/x-font-ghostscript" },
+ { ContentType.Gsheet, "application/vnd.google-apps.spreadsheet" },
+ { ContentType.Gslides, "application/vnd.google-apps.presentation" },
+ { ContentType.Gtar, "application/x-gtar" },
+ { ContentType.Gtm, "application/vnd.groove-tool-message" },
+ { ContentType.Gtw, "model/vnd.gtw" },
+ { ContentType.Gv, "text/vnd.graphviz" },
+ { ContentType.Gxf, "application/gxf" },
+ { ContentType.Gxt, "application/vnd.geonext" },
+ { ContentType.Gz, "application/gzip" },
+ { ContentType.H, "text/x-c" },
+ { ContentType.H261, "video/h261" },
+ { ContentType.H263, "video/h263" },
+ { ContentType.H264, "video/h264" },
+ { ContentType.Hal, "application/vnd.hal+xml" },
+ { ContentType.Hbci, "application/vnd.hbci" },
+ { ContentType.Hbs, "text/x-handlebars-template" },
+ { ContentType.Hdd, "application/x-virtualbox-hdd" },
+ { ContentType.Hdf, "application/x-hdf" },
+ { ContentType.Heic, "image/heic" },
+ { ContentType.Heics, "image/heic-sequence" },
+ { ContentType.Heif, "image/heif" },
+ { ContentType.Heifs, "image/heif-sequence" },
+ { ContentType.Hej2, "image/hej2k" },
+ { ContentType.Held, "application/atsc-held+xml" },
+ { ContentType.Hh, "text/x-c" },
+ { ContentType.Hjson, "application/hjson" },
+ { ContentType.Hlp, "application/winhlp" },
+ { ContentType.Hpgl, "application/vnd.hp-hpgl" },
+ { ContentType.Hpid, "application/vnd.hp-hpid" },
+ { ContentType.Hps, "application/vnd.hp-hps" },
+ { ContentType.Hqx, "application/mac-binhex40" },
+ { ContentType.Hsj2, "image/hsj2" },
+ { ContentType.Htc, "text/x-component" },
+ { ContentType.Htke, "application/vnd.kenameaapp" },
+ { ContentType.Htm, "text/html" },
+ { ContentType.Html, "text/html" },
+ { ContentType.Hvd, "application/vnd.yamaha.hv-dic" },
+ { ContentType.Hvp, "application/vnd.yamaha.hv-voice" },
+ { ContentType.Hvs, "application/vnd.yamaha.hv-script" },
+ { ContentType.I2g, "application/vnd.intergeo" },
+ { ContentType.Icc, "application/vnd.iccprofile" },
+ { ContentType.Ice, "x-conference/x-cooltalk" },
+ { ContentType.Icm, "application/vnd.iccprofile" },
+ { ContentType.Ico, "image/vnd.microsoft.icon" },
+ { ContentType.Ics, "text/calendar" },
+ { ContentType.Ief, "image/ief" },
+ { ContentType.Ifb, "text/calendar" },
+ { ContentType.Ifm, "application/vnd.shana.informed.formdata" },
+ { ContentType.Iges, "model/iges" },
+ { ContentType.Igl, "application/vnd.igloader" },
+ { ContentType.Igm, "application/vnd.insors.igm" },
+ { ContentType.Igs, "model/iges" },
+ { ContentType.Igx, "application/vnd.micrografx.igx" },
+ { ContentType.Iif, "application/vnd.shana.informed.interchange" },
+ { ContentType.Img, "application/octet-stream" },
+ { ContentType.Imp, "application/vnd.accpac.simply.imp" },
+ { ContentType.Ims, "application/vnd.ms-ims" },
+ { ContentType.Ini, "text/plain" },
+ { ContentType.Ink, "application/inkml+xml" },
+ { ContentType.Inkml, "application/inkml+xml" },
+ { ContentType.Install, "application/x-install-instructions" },
+ { ContentType.Iota, "application/vnd.astraea-software.iota" },
+ { ContentType.Ipfix, "application/ipfix" },
+ { ContentType.Ipk, "application/vnd.shana.informed.package" },
+ { ContentType.Irm, "application/vnd.ibm.rights-management" },
+ { ContentType.Irp, "application/vnd.irepository.package+xml" },
+ { ContentType.Iso, "application/octet-stream" },
+ { ContentType.Itp, "application/vnd.shana.informed.formtemplate" },
+ { ContentType.Its, "application/its+xml" },
+ { ContentType.Ivp, "application/vnd.immervision-ivp" },
+ { ContentType.Ivu, "application/vnd.immervision-ivu" },
+ { ContentType.Jad, "text/vnd.sun.j2me.app-descriptor" },
+ { ContentType.Jade, "text/jade" },
+ { ContentType.Jam, "application/vnd.jam" },
+ { ContentType.Jar, "application/java-archive" },
+ { ContentType.Jardiff, "application/x-java-archive-diff" },
+ { ContentType.Java, "text/x-java-source" },
+ { ContentType.Jhc, "image/jphc" },
+ { ContentType.Jisp, "application/vnd.jisp" },
+ { ContentType.Jls, "image/jls" },
+ { ContentType.Jlt, "application/vnd.hp-jlyt" },
+ { ContentType.Jng, "image/x-jng" },
+ { ContentType.Jnlp, "application/x-java-jnlp-file" },
+ { ContentType.Joda, "application/vnd.joost.joda-archive" },
+ { ContentType.Jp2, "image/jp2" },
+ { ContentType.Jpe, "image/jpeg" },
+ { ContentType.Jpeg, "image/jpeg" },
+ { ContentType.Jpf, "image/jpx" },
+ { ContentType.Jpg, "image/jpeg" },
+ { ContentType.Jpg2, "image/jp2" },
+ { ContentType.Jpgm, "video/jpm" },
+ { ContentType.Jpgv, "video/jpeg" },
+ { ContentType.Jph, "image/jph" },
+ { ContentType.Jpm, "image/jpm" },
+ { ContentType.Jpx, "image/jpx" },
+ { ContentType.Javascript, "application/javascript" },
+ { ContentType.Json, "application/json" },
+ { ContentType.Json5, "application/json5" },
+ { ContentType.Jsonld, "application/ld+json" },
+ { ContentType.Jsonml, "application/jsonml+json" },
+ { ContentType.Jsx, "text/jsx" },
+ { ContentType.Jxr, "image/jxr" },
+ { ContentType.Jxra, "image/jxra" },
+ { ContentType.Jxrs, "image/jxrs" },
+ { ContentType.Jxs, "image/jxs" },
+ { ContentType.Jxsc, "image/jxsc" },
+ { ContentType.Jxsi, "image/jxsi" },
+ { ContentType.Jxss, "image/jxss" },
+ { ContentType.Kar, "audio/midi" },
+ { ContentType.Karbon, "application/vnd.kde.karbon" },
+ { ContentType.Kdbx, "application/x-keepass2" },
+ { ContentType.Key, "application/vnd.apple.keynote" },
+ { ContentType.Kfo, "application/vnd.kde.kformula" },
+ { ContentType.Kia, "application/vnd.kidspiration" },
+ { ContentType.Kml, "application/vnd.google-earth.kml+xml" },
+ { ContentType.Kmz, "application/vnd.google-earth.kmz" },
+ { ContentType.Kne, "application/vnd.kinar" },
+ { ContentType.Knp, "application/vnd.kinar" },
+ { ContentType.Kon, "application/vnd.kde.kontour" },
+ { ContentType.Kpr, "application/vnd.kde.kpresenter" },
+ { ContentType.Kpt, "application/vnd.kde.kpresenter" },
+ { ContentType.Kpxx, "application/vnd.ds-keypoint" },
+ { ContentType.Ksp, "application/vnd.kde.kspread" },
+ { ContentType.Ktr, "application/vnd.kahootz" },
+ { ContentType.Ktx, "image/ktx" },
+ { ContentType.Ktx2, "image/ktx2" },
+ { ContentType.Ktz, "application/vnd.kahootz" },
+ { ContentType.Kwd, "application/vnd.kde.kword" },
+ { ContentType.Kwt, "application/vnd.kde.kword" },
+ { ContentType.Lasxml, "application/vnd.las.las+xml" },
+ { ContentType.Latex, "application/x-latex" },
+ { ContentType.Lbd, "application/vnd.llamagraphics.life-balance.desktop" },
+ { ContentType.Lbe, "application/vnd.llamagraphics.life-balance.exchange+xml" },
+ { ContentType.Les, "application/vnd.hhe.lesson-player" },
+ { ContentType.Less, "text/less" },
+ { ContentType.Lgr, "application/lgr+xml" },
+ { ContentType.Lha, "application/x-lzh-compressed" },
+ { ContentType.Link66, "application/vnd.route66.link66+xml" },
+ { ContentType.List, "text/plain" },
+ { ContentType.List3820, "application/vnd.ibm.modcap" },
+ { ContentType.Listafp, "application/vnd.ibm.modcap" },
+ { ContentType.Lnk, "application/x-ms-shortcut" },
+ { ContentType.Log, "text/plain" },
+ { ContentType.Lostxml, "application/lost+xml" },
+ { ContentType.Lrf, "application/octet-stream" },
+ { ContentType.Lrm, "application/vnd.ms-lrm" },
+ { ContentType.Ltf, "application/vnd.frogans.ltf" },
+ { ContentType.Lua, "text/x-lua" },
+ { ContentType.Luac, "application/x-lua-bytecode" },
+ { ContentType.Lvp, "audio/vnd.lucent.voice" },
+ { ContentType.Lwp, "application/vnd.lotus-wordpro" },
+ { ContentType.Lzh, "application/x-lzh-compressed" },
+ { ContentType.M13, "application/x-msmediaview" },
+ { ContentType.M14, "application/x-msmediaview" },
+ { ContentType.M1v, "video/mpeg" },
+ { ContentType.M21, "application/mp21" },
+ { ContentType.M2a, "audio/mpeg" },
+ { ContentType.M2v, "video/mpeg" },
+ { ContentType.M3a, "audio/mpeg" },
+ { ContentType.M3u, "audio/x-mpegurl" },
+ { ContentType.M3u8, "application/vnd.apple.mpegurl" },
+ { ContentType.M4a, "audio/mp4" },
+ { ContentType.M4p, "application/mp4" },
+ { ContentType.M4s, "video/iso.segment" },
+ { ContentType.M4u, "video/vnd.mpegurl" },
+ { ContentType.M4v, "video/x-m4v" },
+ { ContentType.Ma, "application/mathematica" },
+ { ContentType.Mads, "application/mads+xml" },
+ { ContentType.Maei, "application/mmt-aei+xml" },
+ { ContentType.Mag, "application/vnd.ecowin.chart" },
+ { ContentType.Maker, "application/vnd.framemaker" },
+ { ContentType.Man, "text/troff" },
+ { ContentType.Manifest, "text/cache-manifest" },
+ { ContentType.Map, "application/json" },
+ { ContentType.Mar, "application/octet-stream" },
+ { ContentType.Markdown, "text/markdown" },
+ { ContentType.Mathml, "application/mathml+xml" },
+ { ContentType.Mb, "application/mathematica" },
+ { ContentType.Mbk, "application/vnd.mobius.mbk" },
+ { ContentType.Mbox, "application/mbox" },
+ { ContentType.Mc1, "application/vnd.medcalcdata" },
+ { ContentType.Mcd, "application/vnd.mcd" },
+ { ContentType.Mcurl, "text/vnd.curl.mcurl" },
+ { ContentType.Md, "text/markdown" },
+ { ContentType.Mdb, "application/x-msaccess" },
+ { ContentType.Mdi, "image/vnd.ms-modi" },
+ { ContentType.Mdx, "text/mdx" },
+ { ContentType.Me, "text/troff" },
+ { ContentType.Mesh, "model/mesh" },
+ { ContentType.Meta4, "application/metalink4+xml" },
+ { ContentType.Metalink, "application/metalink+xml" },
+ { ContentType.Mets, "application/mets+xml" },
+ { ContentType.Mfm, "application/vnd.mfmp" },
+ { ContentType.Mft, "application/rpki-manifest" },
+ { ContentType.Mgp, "application/vnd.osgeo.mapguide.package" },
+ { ContentType.Mgz, "application/vnd.proteus.magazine" },
+ { ContentType.Mid, "audio/midi" },
+ { ContentType.Midi, "audio/midi" },
+ { ContentType.Mie, "application/x-mie" },
+ { ContentType.Mif, "application/vnd.mif" },
+ { ContentType.Mime, "message/rfc822" },
+ { ContentType.Mj2, "video/mj2" },
+ { ContentType.Mjp2, "video/mj2" },
+ { ContentType.Mjs, "application/javascript" },
+ { ContentType.Mk3d, "video/x-matroska" },
+ { ContentType.Mka, "audio/x-matroska" },
+ { ContentType.Mkd, "text/x-markdown" },
+ { ContentType.Mks, "video/x-matroska" },
+ { ContentType.Mkv, "video/x-matroska" },
+ { ContentType.Mlp, "application/vnd.dolby.mlp" },
+ { ContentType.Mmd, "application/vnd.chipnuts.karaoke-mmd" },
+ { ContentType.Mmf, "application/vnd.smaf" },
+ { ContentType.Mml, "text/mathml" },
+ { ContentType.Mmr, "image/vnd.fujixerox.edmics-mmr" },
+ { ContentType.Mng, "video/x-mng" },
+ { ContentType.Mny, "application/x-msmoney" },
+ { ContentType.Mobi, "application/x-mobipocket-ebook" },
+ { ContentType.Mods, "application/mods+xml" },
+ { ContentType.Mov, "video/quicktime" },
+ { ContentType.Movie, "video/x-sgi-movie" },
+ { ContentType.Mp2, "audio/mpeg" },
+ { ContentType.Mp21, "application/mp21" },
+ { ContentType.Mp2a, "audio/mpeg" },
+ { ContentType.Mp3, "audio/mp3" },
+ { ContentType.Mp4, "video/mp4" },
+ { ContentType.Mp4a, "audio/mp4" },
+ { ContentType.Mp4s, "application/mp4" },
+ { ContentType.Mp4v, "video/mp4" },
+ { ContentType.Mpc, "application/vnd.mophun.certificate" },
+ { ContentType.Mpd, "application/dash+xml" },
+ { ContentType.Mpe, "video/mpeg" },
+ { ContentType.Mpeg, "video/mpeg" },
+ { ContentType.Mpg, "video/mpeg" },
+ { ContentType.Mpg4, "video/mp4" },
+ { ContentType.Mpga, "audio/mpeg" },
+ { ContentType.Mpkg, "application/vnd.apple.installer+xml" },
+ { ContentType.Mpm, "application/vnd.blueice.multipass" },
+ { ContentType.Mpn, "application/vnd.mophun.application" },
+ { ContentType.Mpp, "application/vnd.ms-project" },
+ { ContentType.Mpt, "application/vnd.ms-project" },
+ { ContentType.Mpy, "application/vnd.ibm.minipay" },
+ { ContentType.Mqy, "application/vnd.mobius.mqy" },
+ { ContentType.Mrc, "application/marc" },
+ { ContentType.Mrcx, "application/marcxml+xml" },
+ { ContentType.Ms, "text/troff" },
+ { ContentType.Mscml, "application/mediaservercontrol+xml" },
+ { ContentType.Mseed, "application/vnd.fdsn.mseed" },
+ { ContentType.Mseq, "application/vnd.mseq" },
+ { ContentType.Msf, "application/vnd.epson.msf" },
+ { ContentType.Msg, "application/vnd.ms-outlook" },
+ { ContentType.Msh, "model/mesh" },
+ { ContentType.Msi, "application/octet-stream" },
+ { ContentType.Msl, "application/vnd.mobius.msl" },
+ { ContentType.Msm, "application/octet-stream" },
+ { ContentType.Msp, "application/octet-stream" },
+ { ContentType.Msty, "application/vnd.muvee.style" },
+ { ContentType.Mtl, "model/mtl" },
+ { ContentType.Mts, "model/vnd.mts" },
+ { ContentType.Mus, "application/vnd.musician" },
+ { ContentType.Musd, "application/mmt-usd+xml" },
+ { ContentType.Musicxml, "application/vnd.recordare.musicxml+xml" },
+ { ContentType.Mvb, "application/x-msmediaview" },
+ { ContentType.Mvt, "application/vnd.mapbox-vector-tile" },
+ { ContentType.Mwf, "application/vnd.mfer" },
+ { ContentType.Mxf, "application/mxf" },
+ { ContentType.Mxl, "application/vnd.recordare.musicxml" },
+ { ContentType.Mxmf, "audio/mobile-xmf" },
+ { ContentType.Mxml, "application/xv+xml" },
+ { ContentType.Mxs, "application/vnd.triscape.mxs" },
+ { ContentType.Mxu, "video/vnd.mpegurl" },
+ { ContentType.N3, "text/n3" },
+ { ContentType.Nb, "application/mathematica" },
+ { ContentType.Nbp, "application/vnd.wolfram.player" },
+ { ContentType.Nc, "application/x-netcdf" },
+ { ContentType.Ncx, "application/x-dtbncx+xml" },
+ { ContentType.Nfo, "text/x-nfo" },
+ { ContentType.Ngdat, "application/vnd.nokia.n-gage.data" },
+ { ContentType.Nitf, "application/vnd.nitf" },
+ { ContentType.Nlu, "application/vnd.neurolanguage.nlu" },
+ { ContentType.Nml, "application/vnd.enliven" },
+ { ContentType.Nnd, "application/vnd.noblenet-directory" },
+ { ContentType.Nns, "application/vnd.noblenet-sealer" },
+ { ContentType.Nnw, "application/vnd.noblenet-web" },
+ { ContentType.Npx, "image/vnd.net-fpx" },
+ { ContentType.Nq, "application/n-quads" },
+ { ContentType.Nsc, "application/x-conference" },
+ { ContentType.Nsf, "application/vnd.lotus-notes" },
+ { ContentType.Nt, "application/n-triples" },
+ { ContentType.Ntf, "application/vnd.nitf" },
+ { ContentType.Numbers, "application/vnd.apple.numbers" },
+ { ContentType.Nzb, "application/x-nzb" },
+ { ContentType.Oa2, "application/vnd.fujitsu.oasys2" },
+ { ContentType.Oa3, "application/vnd.fujitsu.oasys3" },
+ { ContentType.Oas, "application/vnd.fujitsu.oasys" },
+ { ContentType.Obd, "application/x-msbinder" },
+ { ContentType.Obgx, "application/vnd.openblox.game+xml" },
+ { ContentType.Obj, "application/x-tgif" },
+ { ContentType.Oda, "application/oda" },
+ { ContentType.Odb, "application/vnd.oasis.opendocument.database" },
+ { ContentType.Odc, "application/vnd.oasis.opendocument.chart" },
+ { ContentType.Odf, "application/vnd.oasis.opendocument.formula" },
+ { ContentType.Odft, "application/vnd.oasis.opendocument.formula-template" },
+ { ContentType.Odg, "application/vnd.oasis.opendocument.graphics" },
+ { ContentType.Odi, "application/vnd.oasis.opendocument.image" },
+ { ContentType.Odm, "application/vnd.oasis.opendocument.text-master" },
+ { ContentType.Odp, "application/vnd.oasis.opendocument.presentation" },
+ { ContentType.Ods, "application/vnd.oasis.opendocument.spreadsheet" },
+ { ContentType.Odt, "application/vnd.oasis.opendocument.text" },
+ { ContentType.Oga, "audio/ogg" },
+ { ContentType.Ogex, "model/vnd.opengex" },
+ { ContentType.Ogg, "audio/ogg" },
+ { ContentType.Ogv, "video/ogg" },
+ { ContentType.Ogx, "application/ogg" },
+ { ContentType.Omdoc, "application/omdoc+xml" },
+ { ContentType.Onepkg, "application/onenote" },
+ { ContentType.Onetmp, "application/onenote" },
+ { ContentType.Onetoc, "application/onenote" },
+ { ContentType.Onetoc2, "application/onenote" },
+ { ContentType.Opf, "application/oebps-package+xml" },
+ { ContentType.Opml, "text/x-opml" },
+ { ContentType.Oprc, "application/vnd.palm" },
+ { ContentType.Opus, "audio/ogg" },
+ { ContentType.Org, "application/vnd.lotus-organizer" },
+ { ContentType.Osf, "application/vnd.yamaha.openscoreformat" },
+ { ContentType.Osfpvg, "application/vnd.yamaha.openscoreformat.osfpvg+xml" },
+ { ContentType.Osm, "application/vnd.openstreetmap.data+xml" },
+ { ContentType.Otc, "application/vnd.oasis.opendocument.chart-template" },
+ { ContentType.Otf, "font/otf" },
+ { ContentType.Otg, "application/vnd.oasis.opendocument.graphics-template" },
+ { ContentType.Oth, "application/vnd.oasis.opendocument.text-web" },
+ { ContentType.Oti, "application/vnd.oasis.opendocument.image-template" },
+ { ContentType.Otp, "application/vnd.oasis.opendocument.presentation-template" },
+ { ContentType.Ots, "application/vnd.oasis.opendocument.spreadsheet-template" },
+ { ContentType.Ott, "application/vnd.oasis.opendocument.text-template" },
+ { ContentType.Ova, "application/x-virtualbox-ova" },
+ { ContentType.Ovf, "application/x-virtualbox-ovf" },
+ { ContentType.Owl, "application/rdf+xml" },
+ { ContentType.Oxps, "application/oxps" },
+ { ContentType.Oxt, "application/vnd.openofficeorg.extension" },
+ { ContentType.P, "text/x-pascal" },
+ { ContentType.P10, "application/pkcs10" },
+ { ContentType.P12, "application/x-pkcs12" },
+ { ContentType.P7b, "application/x-pkcs7-certificates" },
+ { ContentType.P7c, "application/pkcs7-mime" },
+ { ContentType.P7m, "application/pkcs7-mime" },
+ { ContentType.P7r, "application/x-pkcs7-certreqresp" },
+ { ContentType.P7s, "application/pkcs7-signature" },
+ { ContentType.P8, "application/pkcs8" },
+ { ContentType.Pac, "application/x-ns-proxy-autoconfig" },
+ { ContentType.Pages, "application/vnd.apple.pages" },
+ { ContentType.Pas, "text/x-pascal" },
+ { ContentType.Paw, "application/vnd.pawaafile" },
+ { ContentType.Pbd, "application/vnd.powerbuilder6" },
+ { ContentType.Pbm, "image/x-portable-bitmap" },
+ { ContentType.Pcap, "application/vnd.tcpdump.pcap" },
+ { ContentType.Pcf, "application/x-font-pcf" },
+ { ContentType.Pcl, "application/vnd.hp-pcl" },
+ { ContentType.Pclxl, "application/vnd.hp-pclxl" },
+ { ContentType.Pct, "image/x-pict" },
+ { ContentType.Pcurl, "application/vnd.curl.pcurl" },
+ { ContentType.Pcx, "image/vnd.zbrush.pcx" },
+ { ContentType.Pdb, "application/vnd.palm" },
+ { ContentType.Pde, "text/x-processing" },
+ { ContentType.Pdf, "application/pdf" },
+ { ContentType.Pem, "application/x-x509-ca-cert" },
+ { ContentType.Pfa, "application/x-font-type1" },
+ { ContentType.Pfb, "application/x-font-type1" },
+ { ContentType.Pfm, "application/x-font-type1" },
+ { ContentType.Pfr, "application/font-tdpfr" },
+ { ContentType.Pfx, "application/x-pkcs12" },
+ { ContentType.Pgm, "image/x-portable-graymap" },
+ { ContentType.Pgn, "application/x-chess-pgn" },
+ { ContentType.Pgp, "application/pgp-encrypted" },
+ { ContentType.Php, "application/x-httpd-php" },
+ { ContentType.Pic, "image/x-pict" },
+ { ContentType.Pkg, "application/octet-stream" },
+ { ContentType.Pki, "application/pkixcmp" },
+ { ContentType.Pkipath, "application/pkix-pkipath" },
+ { ContentType.Pkpass, "application/vnd.apple.pkpass" },
+ { ContentType.Pl, "application/x-perl" },
+ { ContentType.Plb, "application/vnd.3gpp.pic-bw-large" },
+ { ContentType.Plc, "application/vnd.mobius.plc" },
+ { ContentType.Plf, "application/vnd.pocketlearn" },
+ { ContentType.Pls, "application/pls+xml" },
+ { ContentType.Pm, "application/x-perl" },
+ { ContentType.Pml, "application/vnd.ctc-posml" },
+ { ContentType.Png, "image/png" },
+ { ContentType.Pnm, "image/x-portable-anymap" },
+ { ContentType.Portpkg, "application/vnd.macports.portpkg" },
+ { ContentType.Pot, "application/vnd.ms-powerpoint" },
+ { ContentType.Potm, "application/vnd.ms-powerpoint.template.macroenabled.12" },
+ { ContentType.Potx, "application/vnd.openxmlformats-officedocument.presentationml.template" },
+ { ContentType.Ppam, "application/vnd.ms-powerpoint.addin.macroenabled.12" },
+ { ContentType.Ppd, "application/vnd.cups-ppd" },
+ { ContentType.Ppm, "image/x-portable-pixmap" },
+ { ContentType.Pps, "application/vnd.ms-powerpoint" },
+ { ContentType.Ppsm, "application/vnd.ms-powerpoint.slideshow.macroenabled.12" },
+ { ContentType.Ppsx, "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
+ { ContentType.Ppt, "application/vnd.ms-powerpoint" },
+ { ContentType.Pptm, "application/vnd.ms-powerpoint.presentation.macroenabled.12" },
+ { ContentType.Pptx, "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
+ { ContentType.Pqa, "application/vnd.palm" },
+ { ContentType.Prc, "application/x-mobipocket-ebook" },
+ { ContentType.Pre, "application/vnd.lotus-freelance" },
+ { ContentType.Prf, "application/pics-rules" },
+ { ContentType.Provx, "application/provenance+xml" },
+ { ContentType.Ps, "application/postscript" },
+ { ContentType.Psb, "application/vnd.3gpp.pic-bw-small" },
+ { ContentType.Psd, "image/vnd.adobe.photoshop" },
+ { ContentType.Psf, "application/x-font-linux-psf" },
+ { ContentType.Pskcxml, "application/pskc+xml" },
+ { ContentType.Pti, "image/prs.pti" },
+ { ContentType.Ptid, "application/vnd.pvi.ptid1" },
+ { ContentType.Pub, "application/x-mspublisher" },
+ { ContentType.Pvb, "application/vnd.3gpp.pic-bw-var" },
+ { ContentType.Pwn, "application/vnd.3m.post-it-notes" },
+ { ContentType.Pya, "audio/vnd.ms-playready.media.pya" },
+ { ContentType.Pyv, "video/vnd.ms-playready.media.pyv" },
+ { ContentType.Qam, "application/vnd.epson.quickanime" },
+ { ContentType.Qbo, "application/vnd.intu.qbo" },
+ { ContentType.Qfx, "application/vnd.intu.qfx" },
+ { ContentType.Qps, "application/vnd.publishare-delta-tree" },
+ { ContentType.Qt, "video/quicktime" },
+ { ContentType.Qwd, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qwt, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxb, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxd, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxl, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxt, "application/vnd.quark.quarkxpress" },
+ { ContentType.Ra, "audio/x-pn-realaudio" },
+ { ContentType.Ram, "audio/x-pn-realaudio" },
+ { ContentType.Raml, "application/raml+yaml" },
+ { ContentType.Rapd, "application/route-apd+xml" },
+ { ContentType.Rar, "application/vnd.rar" },
+ { ContentType.Ras, "image/x-cmu-raster" },
+ { ContentType.Rdf, "application/rdf+xml" },
+ { ContentType.Rdz, "application/vnd.data-vision.rdz" },
+ { ContentType.Relo, "application/p2p-overlay+xml" },
+ { ContentType.Rep, "application/vnd.businessobjects" },
+ { ContentType.Res, "application/x-dtbresource+xml" },
+ { ContentType.Rgb, "image/x-rgb" },
+ { ContentType.Rif, "application/reginfo+xml" },
+ { ContentType.Rip, "audio/vnd.rip" },
+ { ContentType.Ris, "application/x-research-info-systems" },
+ { ContentType.Rl, "application/resource-lists+xml" },
+ { ContentType.Rlc, "image/vnd.fujixerox.edmics-rlc" },
+ { ContentType.Rld, "application/resource-lists-diff+xml" },
+ { ContentType.Rm, "application/vnd.rn-realmedia" },
+ { ContentType.Rmi, "audio/midi" },
+ { ContentType.Rmp, "audio/x-pn-realaudio-plugin" },
+ { ContentType.Rms, "application/vnd.jcp.javame.midlet-rms" },
+ { ContentType.Rmvb, "application/vnd.rn-realmedia-vbr" },
+ { ContentType.Rnc, "application/relax-ng-compact-syntax" },
+ { ContentType.Rng, "application/xml" },
+ { ContentType.Roa, "application/rpki-roa" },
+ { ContentType.Roff, "text/troff" },
+ { ContentType.Rp9, "application/vnd.cloanto.rp9" },
+ { ContentType.Rpm, "application/x-redhat-package-manager" },
+ { ContentType.Rpss, "application/vnd.nokia.radio-presets" },
+ { ContentType.Rpst, "application/vnd.nokia.radio-preset" },
+ { ContentType.Rq, "application/sparql-query" },
+ { ContentType.Rs, "application/rls-services+xml" },
+ { ContentType.Rsat, "application/atsc-rsat+xml" },
+ { ContentType.Rsd, "application/rsd+xml" },
+ { ContentType.Rsheet, "application/urc-ressheet+xml" },
+ { ContentType.Rss, "application/rss+xml" },
+ { ContentType.Rtf, "application/rtf" },
+ { ContentType.Rtx, "text/richtext" },
+ { ContentType.Run, "application/x-makeself" },
+ { ContentType.Rusd, "application/route-usd+xml" },
+ { ContentType.S, "text/x-asm" },
+ { ContentType.S3m, "audio/s3m" },
+ { ContentType.Saf, "application/vnd.yamaha.smaf-audio" },
+ { ContentType.Sass, "text/x-sass" },
+ { ContentType.Sbml, "application/sbml+xml" },
+ { ContentType.Sc, "application/vnd.ibm.secure-container" },
+ { ContentType.Scd, "application/x-msschedule" },
+ { ContentType.Scm, "application/vnd.lotus-screencam" },
+ { ContentType.Scq, "application/scvp-cv-request" },
+ { ContentType.Scs, "application/scvp-cv-response" },
+ { ContentType.Scss, "text/x-scss" },
+ { ContentType.Scurl, "text/vnd.curl.scurl" },
+ { ContentType.Sda, "application/vnd.stardivision.draw" },
+ { ContentType.Sdc, "application/vnd.stardivision.calc" },
+ { ContentType.Sdd, "application/vnd.stardivision.impress" },
+ { ContentType.Sdkd, "application/vnd.solent.sdkm+xml" },
+ { ContentType.Sdkm, "application/vnd.solent.sdkm+xml" },
+ { ContentType.Sdp, "application/sdp" },
+ { ContentType.Sdw, "application/vnd.stardivision.writer" },
+ { ContentType.Sea, "application/x-sea" },
+ { ContentType.See, "application/vnd.seemail" },
+ { ContentType.Seed, "application/vnd.fdsn.seed" },
+ { ContentType.Sema, "application/vnd.sema" },
+ { ContentType.Semd, "application/vnd.semd" },
+ { ContentType.Semf, "application/vnd.semf" },
+ { ContentType.Senmlx, "application/senml+xml" },
+ { ContentType.Sensmlx, "application/sensml+xml" },
+ { ContentType.Ser, "application/java-serialized-object" },
+ { ContentType.Setpay, "application/set-payment-initiation" },
+ { ContentType.Setreg, "application/set-registration-initiation" },
+ { ContentType.Sfs, "application/vnd.spotfire.sfs" },
+ { ContentType.Sfv, "text/x-sfv" },
+ { ContentType.Sgi, "image/sgi" },
+ { ContentType.Sgl, "application/vnd.stardivision.writer-global" },
+ { ContentType.Sgm, "text/sgml" },
+ { ContentType.Sgml, "text/sgml" },
+ { ContentType.Sh, "application/x-sh" },
+ { ContentType.Shar, "application/x-shar" },
+ { ContentType.Shex, "text/shex" },
+ { ContentType.Shf, "application/shf+xml" },
+ { ContentType.Shtml, "text/html" },
+ { ContentType.Sid, "image/x-mrsid-image" },
+ { ContentType.Sieve, "application/sieve" },
+ { ContentType.Sig, "application/pgp-signature" },
+ { ContentType.Sil, "audio/silk" },
+ { ContentType.Silo, "model/mesh" },
+ { ContentType.Sis, "application/vnd.symbian.install" },
+ { ContentType.Sisx, "application/vnd.symbian.install" },
+ { ContentType.Sit, "application/x-stuffit" },
+ { ContentType.Sitx, "application/x-stuffitx" },
+ { ContentType.Siv, "application/sieve" },
+ { ContentType.Skd, "application/vnd.koan" },
+ { ContentType.Skm, "application/vnd.koan" },
+ { ContentType.Skp, "application/vnd.koan" },
+ { ContentType.Skt, "application/vnd.koan" },
+ { ContentType.Sldm, "application/vnd.ms-powerpoint.slide.macroenabled.12" },
+ { ContentType.Sldx, "application/vnd.openxmlformats-officedocument.presentationml.slide" },
+ { ContentType.Slim, "text/slim" },
+ { ContentType.Slm, "text/slim" },
+ { ContentType.Sls, "application/route-s-tsid+xml" },
+ { ContentType.Slt, "application/vnd.epson.salt" },
+ { ContentType.Sm, "application/vnd.stepmania.stepchart" },
+ { ContentType.Smf, "application/vnd.stardivision.math" },
+ { ContentType.Smi, "application/smil+xml" },
+ { ContentType.Smil, "application/smil+xml" },
+ { ContentType.Smv, "video/x-smv" },
+ { ContentType.Smzip, "application/vnd.stepmania.package" },
+ { ContentType.Snd, "audio/basic" },
+ { ContentType.Snf, "application/x-font-snf" },
+ { ContentType.So, "application/octet-stream" },
+ { ContentType.Spc, "application/x-pkcs7-certificates" },
+ { ContentType.Spdx, "text/spdx" },
+ { ContentType.Spf, "application/vnd.yamaha.smaf-phrase" },
+ { ContentType.Spl, "application/x-futuresplash" },
+ { ContentType.Spot, "text/vnd.in3d.spot" },
+ { ContentType.Spp, "application/scvp-vp-response" },
+ { ContentType.Spq, "application/scvp-vp-request" },
+ { ContentType.Spx, "audio/ogg" },
+ { ContentType.Sql, "application/x-sql" },
+ { ContentType.Src, "application/x-wais-source" },
+ { ContentType.Srt, "application/x-subrip" },
+ { ContentType.Sru, "application/sru+xml" },
+ { ContentType.Srx, "application/sparql-results+xml" },
+ { ContentType.Ssdl, "application/ssdl+xml" },
+ { ContentType.Sse, "application/vnd.kodak-descriptor" },
+ { ContentType.Ssf, "application/vnd.epson.ssf" },
+ { ContentType.Ssml, "application/ssml+xml" },
+ { ContentType.St, "application/vnd.sailingtracker.track" },
+ { ContentType.Stc, "application/vnd.sun.xml.calc.template" },
+ { ContentType.Std, "application/vnd.sun.xml.draw.template" },
+ { ContentType.Stf, "application/vnd.wt.stf" },
+ { ContentType.Sti, "application/vnd.sun.xml.impress.template" },
+ { ContentType.Stk, "application/hyperstudio" },
+ { ContentType.Stl, "application/vnd.ms-pki.stl" },
+ { ContentType.Stpx, "model/step+xml" },
+ { ContentType.Stpxz, "model/step-xml+zip" },
+ { ContentType.Stpz, "model/step+zip" },
+ { ContentType.Str, "application/vnd.pg.format" },
+ { ContentType.Stw, "application/vnd.sun.xml.writer.template" },
+ { ContentType.Styl, "text/stylus" },
+ { ContentType.Stylus, "text/stylus" },
+ { ContentType.Sub, "image/vnd.dvb.subtitle" },
+ { ContentType.Sus, "application/vnd.sus-calendar" },
+ { ContentType.Susp, "application/vnd.sus-calendar" },
+ { ContentType.Sv4cpio, "application/x-sv4cpio" },
+ { ContentType.Sv4crc, "application/x-sv4crc" },
+ { ContentType.Svc, "application/vnd.dvb.service" },
+ { ContentType.Svd, "application/vnd.svd" },
+ { ContentType.Svg, "image/svg+xml" },
+ { ContentType.Svgz, "image/svg+xml" },
+ { ContentType.Swa, "application/x-director" },
+ { ContentType.Swf, "application/x-shockwave-flash" },
+ { ContentType.Swi, "application/vnd.aristanetworks.swi" },
+ { ContentType.Swidtag, "application/swid+xml" },
+ { ContentType.Sxc, "application/vnd.sun.xml.calc" },
+ { ContentType.Sxd, "application/vnd.sun.xml.draw" },
+ { ContentType.Sxg, "application/vnd.sun.xml.writer.global" },
+ { ContentType.Sxi, "application/vnd.sun.xml.impress" },
+ { ContentType.Sxm, "application/vnd.sun.xml.math" },
+ { ContentType.Sxw, "application/vnd.sun.xml.writer" },
+ { ContentType.T, "text/troff" },
+ { ContentType.T3, "application/x-t3vm-image" },
+ { ContentType.T38, "image/t38" },
+ { ContentType.Taglet, "application/vnd.mynfc" },
+ { ContentType.Tao, "application/vnd.tao.intent-module-archive" },
+ { ContentType.Tap, "image/vnd.tencent.tap" },
+ { ContentType.Tar, "application/x-tar" },
+ { ContentType.Tcap, "application/vnd.3gpp2.tcap" },
+ { ContentType.Tcl, "application/x-tcl" },
+ { ContentType.Td, "application/urc-targetdesc+xml" },
+ { ContentType.Teacher, "application/vnd.smart.teacher" },
+ { ContentType.Tei, "application/tei+xml" },
+ { ContentType.Tex, "application/x-tex" },
+ { ContentType.Texi, "application/x-texinfo" },
+ { ContentType.Texinfo, "application/x-texinfo" },
+ { ContentType.Text, "text/plain" },
+ { ContentType.Tfi, "application/thraud+xml" },
+ { ContentType.Tfm, "application/x-tex-tfm" },
+ { ContentType.Tfx, "image/tiff-fx" },
+ { ContentType.Tga, "image/x-tga" },
+ { ContentType.Thmx, "application/vnd.ms-officetheme" },
+ { ContentType.Tif, "image/tiff" },
+ { ContentType.Tiff, "image/tiff" },
+ { ContentType.Tk, "application/x-tcl" },
+ { ContentType.Tmo, "application/vnd.tmobile-livetv" },
+ { ContentType.Toml, "application/toml" },
+ { ContentType.Torrent, "application/x-bittorrent" },
+ { ContentType.Tpl, "application/vnd.groove-tool-template" },
+ { ContentType.Tpt, "application/vnd.trid.tpt" },
+ { ContentType.Tr, "text/troff" },
+ { ContentType.Tra, "application/vnd.trueapp" },
+ { ContentType.Trig, "application/trig" },
+ { ContentType.Trm, "application/x-msterminal" },
+ { ContentType.Ts, "video/mp2t" },
+ { ContentType.Tsd, "application/timestamped-data" },
+ { ContentType.Tsv, "text/tab-separated-values" },
+ { ContentType.Ttc, "font/collection" },
+ { ContentType.Ttf, "font/ttf" },
+ { ContentType.Ttl, "text/turtle" },
+ { ContentType.Ttml, "application/ttml+xml" },
+ { ContentType.Twd, "application/vnd.simtech-mindmapper" },
+ { ContentType.Twds, "application/vnd.simtech-mindmapper" },
+ { ContentType.Txd, "application/vnd.genomatix.tuxedo" },
+ { ContentType.Txf, "application/vnd.mobius.txf" },
+ { ContentType.Txt, "text/plain" },
+ { ContentType.U32, "application/x-authorware-bin" },
+ { ContentType.U8dsn, "message/global-delivery-status" },
+ { ContentType.U8hdr, "message/global-headers" },
+ { ContentType.U8mdn, "message/global-disposition-notification" },
+ { ContentType.U8msg, "message/global" },
+ { ContentType.Ubj, "application/ubjson" },
+ { ContentType.Udeb, "application/x-debian-package" },
+ { ContentType.Ufd, "application/vnd.ufdl" },
+ { ContentType.Ufdl, "application/vnd.ufdl" },
+ { ContentType.Ulx, "application/x-glulx" },
+ { ContentType.Umj, "application/vnd.umajin" },
+ { ContentType.Unityweb, "application/vnd.unity" },
+ { ContentType.Uoml, "application/vnd.uoml+xml" },
+ { ContentType.Uri, "text/uri-list" },
+ { ContentType.Uris, "text/uri-list" },
+ { ContentType.Urls, "text/uri-list" },
+ { ContentType.Usdz, "model/vnd.usdz+zip" },
+ { ContentType.Ustar, "application/x-ustar" },
+ { ContentType.Utz, "application/vnd.uiq.theme" },
+ { ContentType.Uu, "text/x-uuencode" },
+ { ContentType.Uva, "audio/vnd.dece.audio" },
+ { ContentType.Uvd, "application/vnd.dece.data" },
+ { ContentType.Uvf, "application/vnd.dece.data" },
+ { ContentType.Uvg, "image/vnd.dece.graphic" },
+ { ContentType.Uvh, "video/vnd.dece.hd" },
+ { ContentType.Uvi, "image/vnd.dece.graphic" },
+ { ContentType.Uvm, "video/vnd.dece.mobile" },
+ { ContentType.Uvp, "video/vnd.dece.pd" },
+ { ContentType.Uvs, "video/vnd.dece.sd" },
+ { ContentType.Uvt, "application/vnd.dece.ttml+xml" },
+ { ContentType.Uvu, "video/vnd.uvvu.mp4" },
+ { ContentType.Uvv, "video/vnd.dece.video" },
+ { ContentType.Uvva, "audio/vnd.dece.audio" },
+ { ContentType.Uvvd, "application/vnd.dece.data" },
+ { ContentType.Uvvf, "application/vnd.dece.data" },
+ { ContentType.Uvvg, "image/vnd.dece.graphic" },
+ { ContentType.Uvvh, "video/vnd.dece.hd" },
+ { ContentType.Uvvi, "image/vnd.dece.graphic" },
+ { ContentType.Uvvm, "video/vnd.dece.mobile" },
+ { ContentType.Uvvp, "video/vnd.dece.pd" },
+ { ContentType.Uvvs, "video/vnd.dece.sd" },
+ { ContentType.Uvvt, "application/vnd.dece.ttml+xml" },
+ { ContentType.Uvvu, "video/vnd.uvvu.mp4" },
+ { ContentType.Uvvv, "video/vnd.dece.video" },
+ { ContentType.Uvvx, "application/vnd.dece.unspecified" },
+ { ContentType.Uvvz, "application/vnd.dece.zip" },
+ { ContentType.Uvx, "application/vnd.dece.unspecified" },
+ { ContentType.Uvz, "application/vnd.dece.zip" },
+ { ContentType.Vbox, "application/x-virtualbox-vbox" },
+ { ContentType.Vcard, "text/vcard" },
+ { ContentType.Vcd, "application/x-cdlink" },
+ { ContentType.Vcf, "text/x-vcard" },
+ { ContentType.Vcg, "application/vnd.groove-vcard" },
+ { ContentType.Vcs, "text/x-vcalendar" },
+ { ContentType.Vcx, "application/vnd.vcx" },
+ { ContentType.Vdi, "application/x-virtualbox-vdi" },
+ { ContentType.Vds, "model/vnd.sap.vds" },
+ { ContentType.Vhd, "application/x-virtualbox-vhd" },
+ { ContentType.Vis, "application/vnd.visionary" },
+ { ContentType.Viv, "video/vnd.vivo" },
+ { ContentType.Vmdk, "application/x-virtualbox-vmdk" },
+ { ContentType.Vob, "video/x-ms-vob" },
+ { ContentType.Vor, "application/vnd.stardivision.writer" },
+ { ContentType.Vox, "application/x-authorware-bin" },
+ { ContentType.Vrml, "model/vrml" },
+ { ContentType.Vsd, "application/vnd.visio" },
+ { ContentType.Vsf, "application/vnd.vsf" },
+ { ContentType.Vss, "application/vnd.visio" },
+ { ContentType.Vst, "application/vnd.visio" },
+ { ContentType.Vsw, "application/vnd.visio" },
+ { ContentType.Vtf, "image/vnd.valve.source.texture" },
+ { ContentType.Vtt, "text/vtt" },
+ { ContentType.Vtu, "model/vnd.vtu" },
+ { ContentType.Vxml, "application/voicexml+xml" },
+ { ContentType.W3d, "application/x-director" },
+ { ContentType.Wad, "application/x-doom" },
+ { ContentType.Wadl, "application/vnd.sun.wadl+xml" },
+ { ContentType.War, "application/java-archive" },
+ { ContentType.Wasm, "application/wasm" },
+ { ContentType.Wav, "audio/wav" },
+ { ContentType.Wax, "audio/x-ms-wax" },
+ { ContentType.Wbmp, "image/vnd.wap.wbmp" },
+ { ContentType.Wbs, "application/vnd.criticaltools.wbs+xml" },
+ { ContentType.Wbxml, "application/vnd.wap.wbxml" },
+ { ContentType.Wcm, "application/vnd.ms-works" },
+ { ContentType.Wdb, "application/vnd.ms-works" },
+ { ContentType.Wdp, "image/vnd.ms-photo" },
+ { ContentType.Weba, "audio/webm" },
+ { ContentType.Webapp, "application/x-web-app-manifest+json" },
+ { ContentType.Webm, "video/webm" },
+ { ContentType.Webp, "image/webp" },
+ { ContentType.Wg, "application/vnd.pmi.widget" },
+ { ContentType.Wgt, "application/widget" },
+ { ContentType.Wks, "application/vnd.ms-works" },
+ { ContentType.Wm, "video/x-ms-wm" },
+ { ContentType.Wma, "audio/x-ms-wma" },
+ { ContentType.Wmd, "application/x-ms-wmd" },
+ { ContentType.Wmf, "application/x-msmetafile" },
+ { ContentType.Wml, "text/vnd.wap.wml" },
+ { ContentType.Wmlc, "application/vnd.wap.wmlc" },
+ { ContentType.Wmls, "text/vnd.wap.wmlscript" },
+ { ContentType.Wmlsc, "application/vnd.wap.wmlscriptc" },
+ { ContentType.Wmv, "video/x-ms-wmv" },
+ { ContentType.Wmx, "video/x-ms-wmx" },
+ { ContentType.Wmz, "application/x-ms-wmz" },
+ { ContentType.Woff, "font/woff" },
+ { ContentType.Woff2, "font/woff2" },
+ { ContentType.Wpd, "application/vnd.wordperfect" },
+ { ContentType.Wpl, "application/vnd.ms-wpl" },
+ { ContentType.Wps, "application/vnd.ms-works" },
+ { ContentType.Wqd, "application/vnd.wqd" },
+ { ContentType.Wri, "application/x-mswrite" },
+ { ContentType.Wrl, "model/vrml" },
+ { ContentType.Wsc, "message/vnd.wfa.wsc" },
+ { ContentType.Wsdl, "application/wsdl+xml" },
+ { ContentType.Wspolicy, "application/wspolicy+xml" },
+ { ContentType.Wtb, "application/vnd.webturbo" },
+ { ContentType.Wvx, "video/x-ms-wvx" },
+ { ContentType.X32, "application/x-authorware-bin" },
+ { ContentType.X3d, "model/x3d+xml" },
+ { ContentType.X3db, "model/x3d+binary" },
+ { ContentType.X3dbz, "model/x3d+binary" },
+ { ContentType.X3dv, "model/x3d+vrml" },
+ { ContentType.X3dvz, "model/x3d+vrml" },
+ { ContentType.X3dz, "model/x3d+xml" },
+ { ContentType.Xaml, "application/xaml+xml" },
+ { ContentType.Xap, "application/x-silverlight-app" },
+ { ContentType.Xar, "application/vnd.xara" },
+ { ContentType.Xav, "application/xcap-att+xml" },
+ { ContentType.Xbap, "application/x-ms-xbap" },
+ { ContentType.Xbd, "application/vnd.fujixerox.docuworks.binder" },
+ { ContentType.Xbm, "image/x-xbitmap" },
+ { ContentType.Xca, "application/xcap-caps+xml" },
+ { ContentType.Xcs, "application/calendar+xml" },
+ { ContentType.Xdf, "application/xcap-diff+xml" },
+ { ContentType.Xdm, "application/vnd.syncml.dm+xml" },
+ { ContentType.Xdp, "application/vnd.adobe.xdp+xml" },
+ { ContentType.Xdssc, "application/dssc+xml" },
+ { ContentType.Xdw, "application/vnd.fujixerox.docuworks" },
+ { ContentType.Xel, "application/xcap-el+xml" },
+ { ContentType.Xenc, "application/xenc+xml" },
+ { ContentType.Xer, "application/patch-ops-error+xml" },
+ { ContentType.Xfdf, "application/vnd.adobe.xfdf" },
+ { ContentType.Xfdl, "application/vnd.xfdl" },
+ { ContentType.Xht, "application/xhtml+xml" },
+ { ContentType.Xhtml, "application/xhtml+xml" },
+ { ContentType.Xhvml, "application/xv+xml" },
+ { ContentType.Xif, "image/vnd.xiff" },
+ { ContentType.Xla, "application/vnd.ms-excel" },
+ { ContentType.Xlam, "application/vnd.ms-excel.addin.macroenabled.12" },
+ { ContentType.Xlc, "application/vnd.ms-excel" },
+ { ContentType.Xlf, "application/x-xliff+xml" },
+ { ContentType.Xlm, "application/vnd.ms-excel" },
+ { ContentType.Xls, "application/vnd.ms-excel" },
+ { ContentType.Xlsb, "application/vnd.ms-excel.sheet.binary.macroenabled.12" },
+ { ContentType.Xlsm, "application/vnd.ms-excel.sheet.macroenabled.12" },
+ { ContentType.Xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
+ { ContentType.Xlt, "application/vnd.ms-excel" },
+ { ContentType.Xltm, "application/vnd.ms-excel.template.macroenabled.12" },
+ { ContentType.Xltx, "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
+ { ContentType.Xlw, "application/vnd.ms-excel" },
+ { ContentType.Xm, "audio/xm" },
+ { ContentType.Xml, "application/xml" },
+ { ContentType.Xns, "application/xcap-ns+xml" },
+ { ContentType.Xo, "application/vnd.olpc-sugar" },
+ { ContentType.Xop, "application/xop+xml" },
+ { ContentType.Xpi, "application/x-xpinstall" },
+ { ContentType.Xpl, "application/xproc+xml" },
+ { ContentType.Xpm, "image/x-xpixmap" },
+ { ContentType.Xpr, "application/vnd.is-xpr" },
+ { ContentType.Xps, "application/vnd.ms-xpsdocument" },
+ { ContentType.Xpw, "application/vnd.intercon.formnet" },
+ { ContentType.Xpx, "application/vnd.intercon.formnet" },
+ { ContentType.Xsd, "application/xml" },
+ { ContentType.Xsl, "application/xml" },
+ { ContentType.Xslt, "application/xslt+xml" },
+ { ContentType.Xsm, "application/vnd.syncml+xml" },
+ { ContentType.Xspf, "application/xspf+xml" },
+ { ContentType.Xul, "application/vnd.mozilla.xul+xml" },
+ { ContentType.Xvm, "application/xv+xml" },
+ { ContentType.Xvml, "application/xv+xml" },
+ { ContentType.Xwd, "image/x-xwindowdump" },
+ { ContentType.Xyz, "chemical/x-xyz" },
+ { ContentType.Xz, "application/x-xz" },
+ { ContentType.Yaml, "text/yaml" },
+ { ContentType.Yang, "application/yang" },
+ { ContentType.Yin, "application/yin+xml" },
+ { ContentType.Yml, "text/yaml" },
+ { ContentType.Ymp, "text/x-suse-ymp" },
+ { ContentType.Z1, "application/x-zmachine" },
+ { ContentType.Z2, "application/x-zmachine" },
+ { ContentType.Z3, "application/x-zmachine" },
+ { ContentType.Z4, "application/x-zmachine" },
+ { ContentType.Z5, "application/x-zmachine" },
+ { ContentType.Z6, "application/x-zmachine" },
+ { ContentType.Z7, "application/x-zmachine" },
+ { ContentType.Z8, "application/x-zmachine" },
+ { ContentType.Zaz, "application/vnd.zzazz.deck+xml" },
+ { ContentType.Zip, "application/zip" },
+ { ContentType.Zir, "application/vnd.zul" },
+ { ContentType.Zirz, "application/vnd.zul" },
+ { ContentType.Zmm, "application/vnd.handheld-entertainment+xml" },
+ };
+ private static readonly IReadOnlyDictionary<string, ContentType> ExtensionToCt = new Dictionary<string, ContentType>()
+ {
+ { "aab", ContentType.Aab },
+ { "aac", ContentType.Aac },
+ { "aam", ContentType.Aam },
+ { "aas", ContentType.Aas },
+ { "abw", ContentType.Abw },
+ { "ac", ContentType.Ac },
+ { "acc", ContentType.Acc },
+ { "ace", ContentType.Ace },
+ { "acu", ContentType.Acu },
+ { "acutc", ContentType.Acutc },
+ { "adp", ContentType.Adp },
+ { "aep", ContentType.Aep },
+ { "afm", ContentType.Afm },
+ { "afp", ContentType.Afp },
+ { "ahead", ContentType.Ahead },
+ { "ai", ContentType.Ai },
+ { "aif", ContentType.Aif },
+ { "aifc", ContentType.Aifc },
+ { "aiff", ContentType.Aiff },
+ { "air", ContentType.Air },
+ { "ait", ContentType.Ait },
+ { "ami", ContentType.Ami },
+ { "amr", ContentType.Amr },
+ { "apk", ContentType.Apk },
+ { "apng", ContentType.Apng },
+ { "appcache", ContentType.Appcache },
+ { "apr", ContentType.Apr },
+ { "arc", ContentType.Arc },
+ { "arj", ContentType.Arj },
+ { "asc", ContentType.Asc },
+ { "asf", ContentType.Asf },
+ { "asm", ContentType.Asm },
+ { "aso", ContentType.Aso },
+ { "asx", ContentType.Asx },
+ { "atc", ContentType.Atc },
+ { "atom", ContentType.Atom },
+ { "atomcat", ContentType.Atomcat },
+ { "atomsvc", ContentType.Atomsvc },
+ { "atx", ContentType.Atx },
+ { "au", ContentType.Au },
+ { "avi", ContentType.Avi },
+ { "avif", ContentType.Avif },
+ { "aw", ContentType.Aw },
+ { "azf", ContentType.Azf },
+ { "azs", ContentType.Azs },
+ { "azv", ContentType.Azv },
+ { "azw", ContentType.Azw },
+ { "b16", ContentType.B16 },
+ { "bat", ContentType.Bat },
+ { "bcpio", ContentType.Bcpio },
+ { "bdf", ContentType.Bdf },
+ { "bdm", ContentType.Bdm },
+ { "bdoc", ContentType.Bdoc },
+ { "bed", ContentType.Bed },
+ { "bh2", ContentType.Bh2 },
+ { "bin", ContentType.Binary },
+ { "blb", ContentType.Blb },
+ { "blorb", ContentType.Blorb },
+ { "bmi", ContentType.Bmi },
+ { "bmml", ContentType.Bmml },
+ { "bmp", ContentType.Bmp },
+ { "book", ContentType.Book },
+ { "box", ContentType.Box },
+ { "boz", ContentType.Boz },
+ { "bpk", ContentType.Bpk },
+ { "bsp", ContentType.Bsp },
+ { "btif", ContentType.Btif },
+ { "buffer", ContentType.Buffer },
+ { "bz", ContentType.Bz },
+ { "bz2", ContentType.Bz2 },
+ { "c", ContentType.C },
+ { "c11amc", ContentType.C11amc },
+ { "c11amz", ContentType.C11amz },
+ { "c4d", ContentType.C4d },
+ { "c4f", ContentType.C4f },
+ { "c4g", ContentType.C4g },
+ { "c4p", ContentType.C4p },
+ { "c4u", ContentType.C4u },
+ { "cab", ContentType.Cab },
+ { "caf", ContentType.Caf },
+ { "cap", ContentType.Cap },
+ { "car", ContentType.Car },
+ { "cat", ContentType.Cat },
+ { "cb7", ContentType.Cb7 },
+ { "cba", ContentType.Cba },
+ { "cbr", ContentType.Cbr },
+ { "cbt", ContentType.Cbt },
+ { "cbz", ContentType.Cbz },
+ { "cc", ContentType.Cc },
+ { "cco", ContentType.Cco },
+ { "cct", ContentType.Cct },
+ { "ccxml", ContentType.Ccxml },
+ { "cdbcmsg", ContentType.Cdbcmsg },
+ { "cdf", ContentType.Cdf },
+ { "cdfx", ContentType.Cdfx },
+ { "cdkey", ContentType.Cdkey },
+ { "cdmia", ContentType.Cdmia },
+ { "cdmic", ContentType.Cdmic },
+ { "cdmid", ContentType.Cdmid },
+ { "cdmio", ContentType.Cdmio },
+ { "cdmiq", ContentType.Cdmiq },
+ { "cdx", ContentType.Cdx },
+ { "cdxml", ContentType.Cdxml },
+ { "cdy", ContentType.Cdy },
+ { "cer", ContentType.Cer },
+ { "cfs", ContentType.Cfs },
+ { "cgm", ContentType.Cgm },
+ { "chat", ContentType.Chat },
+ { "chm", ContentType.Chm },
+ { "chrt", ContentType.Chrt },
+ { "cif", ContentType.Cif },
+ { "cii", ContentType.Cii },
+ { "cil", ContentType.Cil },
+ { "cjs", ContentType.Cjs },
+ { "cla", ContentType.Cla },
+ { "clkk", ContentType.Clkk },
+ { "clkp", ContentType.Clkp },
+ { "clkt", ContentType.Clkt },
+ { "clkw", ContentType.Clkw },
+ { "clkx", ContentType.Clkx },
+ { "clp", ContentType.Clp },
+ { "cmc", ContentType.Cmc },
+ { "cmdf", ContentType.Cmdf },
+ { "cml", ContentType.Cml },
+ { "cmp", ContentType.Cmp },
+ { "cmx", ContentType.Cmx },
+ { "cod", ContentType.Cod },
+ { "coffee", ContentType.Coffee },
+ { "com", ContentType.Com },
+ { "conf", ContentType.Conf },
+ { "cpio", ContentType.Cpio },
+ { "cpp", ContentType.Cpp },
+ { "cpt", ContentType.Cpt },
+ { "crd", ContentType.Crd },
+ { "crl", ContentType.Crl },
+ { "crt", ContentType.Crt },
+ { "crx", ContentType.Crx },
+ { "csh", ContentType.Csh },
+ { "csl", ContentType.Csl },
+ { "csml", ContentType.Csml },
+ { "csp", ContentType.Csp },
+ { "css", ContentType.Css },
+ { "cst", ContentType.Cst },
+ { "csv", ContentType.Csv },
+ { "cu", ContentType.Cu },
+ { "curl", ContentType.Curl },
+ { "cww", ContentType.Cww },
+ { "cxt", ContentType.Cxt },
+ { "cxx", ContentType.Cxx },
+ { "dae", ContentType.Dae },
+ { "daf", ContentType.Daf },
+ { "dart", ContentType.Dart },
+ { "dataless", ContentType.Dataless },
+ { "davmount", ContentType.Davmount },
+ { "dbf", ContentType.Dbf },
+ { "dbk", ContentType.Dbk },
+ { "dcr", ContentType.Dcr },
+ { "dcurl", ContentType.Dcurl },
+ { "dd2", ContentType.Dd2 },
+ { "ddd", ContentType.Ddd },
+ { "ddf", ContentType.Ddf },
+ { "dds", ContentType.Dds },
+ { "deb", ContentType.Deb },
+ { "def", ContentType.Def },
+ { "deploy", ContentType.Deploy },
+ { "der", ContentType.Der },
+ { "dfac", ContentType.Dfac },
+ { "dgc", ContentType.Dgc },
+ { "dic", ContentType.Dic },
+ { "dir", ContentType.Dir },
+ { "dis", ContentType.Dis },
+ { "dist", ContentType.Dist },
+ { "distz", ContentType.Distz },
+ { "djv", ContentType.Djv },
+ { "djvu", ContentType.Djvu },
+ { "dll", ContentType.Dll },
+ { "dmg", ContentType.Dmg },
+ { "dmp", ContentType.Dmp },
+ { "dms", ContentType.Dms },
+ { "dna", ContentType.Dna },
+ { "doc", ContentType.Doc },
+ { "docm", ContentType.Docm },
+ { "docx", ContentType.Docx },
+ { "dot", ContentType.Dot },
+ { "dotm", ContentType.Dotm },
+ { "dotx", ContentType.Dotx },
+ { "dp", ContentType.Dp },
+ { "dpg", ContentType.Dpg },
+ { "dra", ContentType.Dra },
+ { "drle", ContentType.Drle },
+ { "dsc", ContentType.Dsc },
+ { "dssc", ContentType.Dssc },
+ { "dtb", ContentType.Dtb },
+ { "dtd", ContentType.Dtd },
+ { "dts", ContentType.Dts },
+ { "dtshd", ContentType.Dtshd },
+ { "dump", ContentType.Dump },
+ { "dvb", ContentType.Dvb },
+ { "dvi", ContentType.Dvi },
+ { "dwd", ContentType.Dwd },
+ { "dwf", ContentType.Dwf },
+ { "dwg", ContentType.Dwg },
+ { "dxf", ContentType.Dxf },
+ { "dxp", ContentType.Dxp },
+ { "dxr", ContentType.Dxr },
+ { "ear", ContentType.Ear },
+ { "ecma", ContentType.Ecma },
+ { "edm", ContentType.Edm },
+ { "edx", ContentType.Edx },
+ { "efif", ContentType.Efif },
+ { "ei6", ContentType.Ei6 },
+ { "elc", ContentType.Elc },
+ { "emf", ContentType.Emf },
+ { "eml", ContentType.Eml },
+ { "emma", ContentType.Emma },
+ { "emz", ContentType.Emz },
+ { "eol", ContentType.Eol },
+ { "eot", ContentType.Eot },
+ { "eps", ContentType.Eps },
+ { "epub", ContentType.Epub },
+ { "es", ContentType.Es },
+ { "es3", ContentType.Es3 },
+ { "esa", ContentType.Esa },
+ { "esf", ContentType.Esf },
+ { "et3", ContentType.Et3 },
+ { "etx", ContentType.Etx },
+ { "eva", ContentType.Eva },
+ { "evy", ContentType.Evy },
+ { "exe", ContentType.Exe },
+ { "exi", ContentType.Exi },
+ { "exp", ContentType.Exp },
+ { "exr", ContentType.Exr },
+ { "ext", ContentType.Ext },
+ { "ez", ContentType.Ez },
+ { "ez2", ContentType.Ez2 },
+ { "ez3", ContentType.Ez3 },
+ { "f", ContentType.F },
+ { "f4v", ContentType.F4v },
+ { "f77", ContentType.Fortran },
+ { "f90", ContentType.F90 },
+ { "fbs", ContentType.Fbs },
+ { "fcdt", ContentType.Fcdt },
+ { "fcs", ContentType.Fcs },
+ { "fdf", ContentType.Fdf },
+ { "fdt", ContentType.Fdt },
+ { "fg5", ContentType.Fg5 },
+ { "fgd", ContentType.Fgd },
+ { "fh", ContentType.Fh },
+ { "fh4", ContentType.Fh4 },
+ { "fh5", ContentType.Fh5 },
+ { "fh7", ContentType.Fh7 },
+ { "fhc", ContentType.Fhc },
+ { "fig", ContentType.Fig },
+ { "fits", ContentType.Fits },
+ { "flac", ContentType.Flac },
+ { "fli", ContentType.Fli },
+ { "flo", ContentType.Flo },
+ { "flv", ContentType.Flv },
+ { "flw", ContentType.Flw },
+ { "flx", ContentType.Flx },
+ { "fly", ContentType.Fly },
+ { "fm", ContentType.Fm },
+ { "fnc", ContentType.Fnc },
+ { "fo", ContentType.Fo },
+ { "for", ContentType.For },
+ { "fpx", ContentType.Fpx },
+ { "frame", ContentType.Frame },
+ { "fsc", ContentType.Fsc },
+ { "fst", ContentType.Fst },
+ { "ftc", ContentType.Ftc },
+ { "fti", ContentType.Fti },
+ { "fvt", ContentType.Fvt },
+ { "fxp", ContentType.Fxp },
+ { "fxpl", ContentType.Fxpl },
+ { "fzs", ContentType.Fzs },
+ { "g2w", ContentType.G2w },
+ { "g3", ContentType.G3 },
+ { "g3w", ContentType.G3w },
+ { "gac", ContentType.Gac },
+ { "gam", ContentType.Gam },
+ { "gbr", ContentType.Gbr },
+ { "gca", ContentType.Gca },
+ { "gdl", ContentType.Gdl },
+ { "gdoc", ContentType.Gdoc },
+ { "geo", ContentType.Geo },
+ { "geojson", ContentType.Geojson },
+ { "gex", ContentType.Gex },
+ { "ggb", ContentType.Ggb },
+ { "ggt", ContentType.Ggt },
+ { "ghf", ContentType.Ghf },
+ { "gif", ContentType.Gif },
+ { "gim", ContentType.Gim },
+ { "glb", ContentType.Glb },
+ { "gltf", ContentType.Gltf },
+ { "gml", ContentType.Gml },
+ { "gmx", ContentType.Gmx },
+ { "gnumeric", ContentType.Gnumeric },
+ { "gph", ContentType.Gph },
+ { "gpx", ContentType.Gpx },
+ { "gqf", ContentType.Gqf },
+ { "gqs", ContentType.Gqs },
+ { "gram", ContentType.Gram },
+ { "gramps", ContentType.Gramps },
+ { "gre", ContentType.Gre },
+ { "grv", ContentType.Grv },
+ { "grxml", ContentType.Grxml },
+ { "gsf", ContentType.Gsf },
+ { "gsheet", ContentType.Gsheet },
+ { "gslides", ContentType.Gslides },
+ { "gtar", ContentType.Gtar },
+ { "gtm", ContentType.Gtm },
+ { "gtw", ContentType.Gtw },
+ { "gv", ContentType.Gv },
+ { "gxf", ContentType.Gxf },
+ { "gxt", ContentType.Gxt },
+ { "gz", ContentType.Gz },
+ { "h", ContentType.H },
+ { "h261", ContentType.H261 },
+ { "h263", ContentType.H263 },
+ { "h264", ContentType.H264 },
+ { "hal", ContentType.Hal },
+ { "hbci", ContentType.Hbci },
+ { "hbs", ContentType.Hbs },
+ { "hdd", ContentType.Hdd },
+ { "hdf", ContentType.Hdf },
+ { "heic", ContentType.Heic },
+ { "heics", ContentType.Heics },
+ { "heif", ContentType.Heif },
+ { "heifs", ContentType.Heifs },
+ { "hej2", ContentType.Hej2 },
+ { "held", ContentType.Held },
+ { "hh", ContentType.Hh },
+ { "hjson", ContentType.Hjson },
+ { "hlp", ContentType.Hlp },
+ { "hpgl", ContentType.Hpgl },
+ { "hpid", ContentType.Hpid },
+ { "hps", ContentType.Hps },
+ { "hqx", ContentType.Hqx },
+ { "hsj2", ContentType.Hsj2 },
+ { "htc", ContentType.Htc },
+ { "htke", ContentType.Htke },
+ { "htm", ContentType.Htm },
+ { "html", ContentType.Html },
+ { "hvd", ContentType.Hvd },
+ { "hvp", ContentType.Hvp },
+ { "hex", ContentType.Binary },
+ { "hvs", ContentType.Hvs },
+ { "i2g", ContentType.I2g },
+ { "icc", ContentType.Icc },
+ { "ice", ContentType.Ice },
+ { "icm", ContentType.Icm },
+ { "ico", ContentType.Ico },
+ { "ics", ContentType.Ics },
+ { "ief", ContentType.Ief },
+ { "ifb", ContentType.Ifb },
+ { "ifm", ContentType.Ifm },
+ { "iges", ContentType.Iges },
+ { "igl", ContentType.Igl },
+ { "igm", ContentType.Igm },
+ { "igs", ContentType.Igs },
+ { "igx", ContentType.Igx },
+ { "iif", ContentType.Iif },
+ { "img", ContentType.Img },
+ { "imp", ContentType.Imp },
+ { "ims", ContentType.Ims },
+ { "ini", ContentType.Ini },
+ { "ink", ContentType.Ink },
+ { "inkml", ContentType.Inkml },
+ { "install", ContentType.Install },
+ { "iota", ContentType.Iota },
+ { "ipfix", ContentType.Ipfix },
+ { "ipk", ContentType.Ipk },
+ { "irm", ContentType.Irm },
+ { "irp", ContentType.Irp },
+ { "iso", ContentType.Iso },
+ { "itp", ContentType.Itp },
+ { "its", ContentType.Its },
+ { "ivp", ContentType.Ivp },
+ { "ivu", ContentType.Ivu },
+ { "jad", ContentType.Jad },
+ { "jade", ContentType.Jade },
+ { "jam", ContentType.Jam },
+ { "jar", ContentType.Jar },
+ { "jardiff", ContentType.Jardiff },
+ { "java", ContentType.Java },
+ { "jhc", ContentType.Jhc },
+ { "jisp", ContentType.Jisp },
+ { "jls", ContentType.Jls },
+ { "jlt", ContentType.Jlt },
+ { "jng", ContentType.Jng },
+ { "jnlp", ContentType.Jnlp },
+ { "joda", ContentType.Joda },
+ { "jp2", ContentType.Jp2 },
+ { "jpe", ContentType.Jpe },
+ { "jpeg", ContentType.Jpeg },
+ { "jpf", ContentType.Jpf },
+ { "jpg", ContentType.Jpg },
+ { "jpg2", ContentType.Jpg2 },
+ { "jpgm", ContentType.Jpgm },
+ { "jpgv", ContentType.Jpgv },
+ { "jph", ContentType.Jph },
+ { "jpm", ContentType.Jpm },
+ { "jpx", ContentType.Jpx },
+ { "js", ContentType.Javascript },
+ { "json", ContentType.Json },
+ { "json5", ContentType.Json5 },
+ { "jsonld", ContentType.Jsonld },
+ { "jsonml", ContentType.Jsonml },
+ { "jsx", ContentType.Jsx },
+ { "jxr", ContentType.Jxr },
+ { "jxra", ContentType.Jxra },
+ { "jxrs", ContentType.Jxrs },
+ { "jxs", ContentType.Jxs },
+ { "jxsc", ContentType.Jxsc },
+ { "jxsi", ContentType.Jxsi },
+ { "jxss", ContentType.Jxss },
+ { "kar", ContentType.Kar },
+ { "karbon", ContentType.Karbon },
+ { "kdbx", ContentType.Kdbx },
+ { "key", ContentType.Key },
+ { "kfo", ContentType.Kfo },
+ { "kia", ContentType.Kia },
+ { "kml", ContentType.Kml },
+ { "kmz", ContentType.Kmz },
+ { "kne", ContentType.Kne },
+ { "knp", ContentType.Knp },
+ { "kon", ContentType.Kon },
+ { "kpr", ContentType.Kpr },
+ { "kpt", ContentType.Kpt },
+ { "kpxx", ContentType.Kpxx },
+ { "ksp", ContentType.Ksp },
+ { "ktr", ContentType.Ktr },
+ { "ktx", ContentType.Ktx },
+ { "ktx2", ContentType.Ktx2 },
+ { "ktz", ContentType.Ktz },
+ { "kwd", ContentType.Kwd },
+ { "kwt", ContentType.Kwt },
+ { "lasxml", ContentType.Lasxml },
+ { "latex", ContentType.Latex },
+ { "lbd", ContentType.Lbd },
+ { "lbe", ContentType.Lbe },
+ { "les", ContentType.Les },
+ { "less", ContentType.Less },
+ { "lgr", ContentType.Lgr },
+ { "lha", ContentType.Lha },
+ { "link66", ContentType.Link66 },
+ { "list", ContentType.List },
+ { "list3820", ContentType.List3820 },
+ { "listafp", ContentType.Listafp },
+ { "lnk", ContentType.Lnk },
+ { "log", ContentType.Log },
+ { "lostxml", ContentType.Lostxml },
+ { "lrf", ContentType.Lrf },
+ { "lrm", ContentType.Lrm },
+ { "ltf", ContentType.Ltf },
+ { "lua", ContentType.Lua },
+ { "luac", ContentType.Luac },
+ { "lvp", ContentType.Lvp },
+ { "lwp", ContentType.Lwp },
+ { "lzh", ContentType.Lzh },
+ { "m13", ContentType.M13 },
+ { "m14", ContentType.M14 },
+ { "m1v", ContentType.M1v },
+ { "m21", ContentType.M21 },
+ { "m2a", ContentType.M2a },
+ { "m2v", ContentType.M2v },
+ { "m3a", ContentType.M3a },
+ { "m3u", ContentType.M3u },
+ { "m3u8", ContentType.M3u8 },
+ { "m4a", ContentType.M4a },
+ { "m4p", ContentType.M4p },
+ { "m4s", ContentType.M4s },
+ { "m4u", ContentType.M4u },
+ { "m4v", ContentType.M4v },
+ { "ma", ContentType.Ma },
+ { "mads", ContentType.Mads },
+ { "maei", ContentType.Maei },
+ { "mag", ContentType.Mag },
+ { "maker", ContentType.Maker },
+ { "man", ContentType.Man },
+ { "manifest", ContentType.Manifest },
+ { "map", ContentType.Map },
+ { "mar", ContentType.Mar },
+ { "markdown", ContentType.Markdown },
+ { "mathml", ContentType.Mathml },
+ { "mb", ContentType.Mb },
+ { "mbk", ContentType.Mbk },
+ { "mbox", ContentType.Mbox },
+ { "mc1", ContentType.Mc1 },
+ { "mcd", ContentType.Mcd },
+ { "mcurl", ContentType.Mcurl },
+ { "md", ContentType.Md },
+ { "mdb", ContentType.Mdb },
+ { "mdi", ContentType.Mdi },
+ { "mdx", ContentType.Mdx },
+ { "me", ContentType.Me },
+ { "mesh", ContentType.Mesh },
+ { "meta4", ContentType.Meta4 },
+ { "metalink", ContentType.Metalink },
+ { "mets", ContentType.Mets },
+ { "mfm", ContentType.Mfm },
+ { "mft", ContentType.Mft },
+ { "mgp", ContentType.Mgp },
+ { "mgz", ContentType.Mgz },
+ { "mid", ContentType.Mid },
+ { "midi", ContentType.Midi },
+ { "mie", ContentType.Mie },
+ { "mif", ContentType.Mif },
+ { "mime", ContentType.Mime },
+ { "mj2", ContentType.Mj2 },
+ { "mjp2", ContentType.Mjp2 },
+ { "mjs", ContentType.Mjs },
+ { "mk3d", ContentType.Mk3d },
+ { "mka", ContentType.Mka },
+ { "mkd", ContentType.Mkd },
+ { "mks", ContentType.Mks },
+ { "mkv", ContentType.Mkv },
+ { "mlp", ContentType.Mlp },
+ { "mmd", ContentType.Mmd },
+ { "mmf", ContentType.Mmf },
+ { "mml", ContentType.Mml },
+ { "mmr", ContentType.Mmr },
+ { "mng", ContentType.Mng },
+ { "mny", ContentType.Mny },
+ { "mobi", ContentType.Mobi },
+ { "mods", ContentType.Mods },
+ { "mov", ContentType.Mov },
+ { "movie", ContentType.Movie },
+ { "mp2", ContentType.Mp2 },
+ { "mp21", ContentType.Mp21 },
+ { "mp2a", ContentType.Mp2a },
+ { "mp3", ContentType.Mp3 },
+ { "mp4", ContentType.Mp4 },
+ { "mp4a", ContentType.Mp4a },
+ { "mp4s", ContentType.Mp4s },
+ { "mp4v", ContentType.Mp4v },
+ { "mpc", ContentType.Mpc },
+ { "mpd", ContentType.Mpd },
+ { "mpe", ContentType.Mpe },
+ { "mpeg", ContentType.Mpeg },
+ { "mpg", ContentType.Mpg },
+ { "mpg4", ContentType.Mpg4 },
+ { "mpga", ContentType.Mpga },
+ { "mpkg", ContentType.Mpkg },
+ { "mpm", ContentType.Mpm },
+ { "mpn", ContentType.Mpn },
+ { "mpp", ContentType.Mpp },
+ { "mpt", ContentType.Mpt },
+ { "mpy", ContentType.Mpy },
+ { "mqy", ContentType.Mqy },
+ { "mrc", ContentType.Mrc },
+ { "mrcx", ContentType.Mrcx },
+ { "ms", ContentType.Ms },
+ { "mscml", ContentType.Mscml },
+ { "mseed", ContentType.Mseed },
+ { "mseq", ContentType.Mseq },
+ { "msf", ContentType.Msf },
+ { "msg", ContentType.Msg },
+ { "msh", ContentType.Msh },
+ { "msi", ContentType.Msi },
+ { "msl", ContentType.Msl },
+ { "msm", ContentType.Msm },
+ { "msp", ContentType.Msp },
+ { "msty", ContentType.Msty },
+ { "mtl", ContentType.Mtl },
+ { "mts", ContentType.Mts },
+ { "mus", ContentType.Mus },
+ { "musd", ContentType.Musd },
+ { "musicxml", ContentType.Musicxml },
+ { "mvb", ContentType.Mvb },
+ { "mvt", ContentType.Mvt },
+ { "mwf", ContentType.Mwf },
+ { "mxf", ContentType.Mxf },
+ { "mxl", ContentType.Mxl },
+ { "mxmf", ContentType.Mxmf },
+ { "mxml", ContentType.Mxml },
+ { "mxs", ContentType.Mxs },
+ { "mxu", ContentType.Mxu },
+ { "n3", ContentType.N3 },
+ { "nb", ContentType.Nb },
+ { "nbp", ContentType.Nbp },
+ { "nc", ContentType.Nc },
+ { "ncx", ContentType.Ncx },
+ { "nfo", ContentType.Nfo },
+ { "ngdat", ContentType.Ngdat },
+ { "nitf", ContentType.Nitf },
+ { "nlu", ContentType.Nlu },
+ { "nml", ContentType.Nml },
+ { "nnd", ContentType.Nnd },
+ { "nns", ContentType.Nns },
+ { "nnw", ContentType.Nnw },
+ { "npx", ContentType.Npx },
+ { "nq", ContentType.Nq },
+ { "nsc", ContentType.Nsc },
+ { "nsf", ContentType.Nsf },
+ { "nt", ContentType.Nt },
+ { "ntf", ContentType.Ntf },
+ { "numbers", ContentType.Numbers },
+ { "nzb", ContentType.Nzb },
+ { "oa2", ContentType.Oa2 },
+ { "oa3", ContentType.Oa3 },
+ { "oas", ContentType.Oas },
+ { "obd", ContentType.Obd },
+ { "obgx", ContentType.Obgx },
+ { "obj", ContentType.Obj },
+ { "oda", ContentType.Oda },
+ { "odb", ContentType.Odb },
+ { "odc", ContentType.Odc },
+ { "odf", ContentType.Odf },
+ { "odft", ContentType.Odft },
+ { "odg", ContentType.Odg },
+ { "odi", ContentType.Odi },
+ { "odm", ContentType.Odm },
+ { "odp", ContentType.Odp },
+ { "ods", ContentType.Ods },
+ { "odt", ContentType.Odt },
+ { "oga", ContentType.Oga },
+ { "ogex", ContentType.Ogex },
+ { "ogg", ContentType.Ogg },
+ { "ogv", ContentType.Ogv },
+ { "ogx", ContentType.Ogx },
+ { "omdoc", ContentType.Omdoc },
+ { "onepkg", ContentType.Onepkg },
+ { "onetmp", ContentType.Onetmp },
+ { "onetoc", ContentType.Onetoc },
+ { "onetoc2", ContentType.Onetoc2 },
+ { "opf", ContentType.Opf },
+ { "opml", ContentType.Opml },
+ { "oprc", ContentType.Oprc },
+ { "opus", ContentType.Opus },
+ { "org", ContentType.Org },
+ { "osf", ContentType.Osf },
+ { "osfpvg", ContentType.Osfpvg },
+ { "osm", ContentType.Osm },
+ { "otc", ContentType.Otc },
+ { "otf", ContentType.Otf },
+ { "otg", ContentType.Otg },
+ { "oth", ContentType.Oth },
+ { "oti", ContentType.Oti },
+ { "otp", ContentType.Otp },
+ { "ots", ContentType.Ots },
+ { "ott", ContentType.Ott },
+ { "ova", ContentType.Ova },
+ { "ovf", ContentType.Ovf },
+ { "owl", ContentType.Owl },
+ { "oxps", ContentType.Oxps },
+ { "oxt", ContentType.Oxt },
+ { "p", ContentType.P },
+ { "p10", ContentType.P10 },
+ { "p12", ContentType.P12 },
+ { "p7b", ContentType.P7b },
+ { "p7c", ContentType.P7c },
+ { "p7m", ContentType.P7m },
+ { "p7r", ContentType.P7r },
+ { "p7s", ContentType.P7s },
+ { "p8", ContentType.P8 },
+ { "pac", ContentType.Pac },
+ { "pages", ContentType.Pages },
+ { "pas", ContentType.Pas },
+ { "paw", ContentType.Paw },
+ { "pbd", ContentType.Pbd },
+ { "pbm", ContentType.Pbm },
+ { "pcap", ContentType.Pcap },
+ { "pcf", ContentType.Pcf },
+ { "pcl", ContentType.Pcl },
+ { "pclxl", ContentType.Pclxl },
+ { "pct", ContentType.Pct },
+ { "pcurl", ContentType.Pcurl },
+ { "pcx", ContentType.Pcx },
+ { "pdb", ContentType.Pdb },
+ { "pde", ContentType.Pde },
+ { "pdf", ContentType.Pdf },
+ { "pem", ContentType.Pem },
+ { "pfa", ContentType.Pfa },
+ { "pfb", ContentType.Pfb },
+ { "pfm", ContentType.Pfm },
+ { "pfr", ContentType.Pfr },
+ { "pfx", ContentType.Pfx },
+ { "pgm", ContentType.Pgm },
+ { "pgn", ContentType.Pgn },
+ { "pgp", ContentType.Pgp },
+ { "php", ContentType.Php },
+ { "pic", ContentType.Pic },
+ { "pkg", ContentType.Pkg },
+ { "pki", ContentType.Pki },
+ { "pkipath", ContentType.Pkipath },
+ { "pkpass", ContentType.Pkpass },
+ { "pl", ContentType.Pl },
+ { "plb", ContentType.Plb },
+ { "plc", ContentType.Plc },
+ { "plf", ContentType.Plf },
+ { "pls", ContentType.Pls },
+ { "pm", ContentType.Pm },
+ { "pml", ContentType.Pml },
+ { "png", ContentType.Png },
+ { "pnm", ContentType.Pnm },
+ { "portpkg", ContentType.Portpkg },
+ { "pot", ContentType.Pot },
+ { "potm", ContentType.Potm },
+ { "potx", ContentType.Potx },
+ { "ppam", ContentType.Ppam },
+ { "ppd", ContentType.Ppd },
+ { "ppm", ContentType.Ppm },
+ { "pps", ContentType.Pps },
+ { "ppsm", ContentType.Ppsm },
+ { "ppsx", ContentType.Ppsx },
+ { "ppt", ContentType.Ppt },
+ { "pptm", ContentType.Pptm },
+ { "pptx", ContentType.Pptx },
+ { "pqa", ContentType.Pqa },
+ { "prc", ContentType.Prc },
+ { "pre", ContentType.Pre },
+ { "prf", ContentType.Prf },
+ { "provx", ContentType.Provx },
+ { "ps", ContentType.Ps },
+ { "psb", ContentType.Psb },
+ { "psd", ContentType.Psd },
+ { "psf", ContentType.Psf },
+ { "pskcxml", ContentType.Pskcxml },
+ { "pti", ContentType.Pti },
+ { "ptid", ContentType.Ptid },
+ { "pub", ContentType.Pub },
+ { "pvb", ContentType.Pvb },
+ { "pwn", ContentType.Pwn },
+ { "pya", ContentType.Pya },
+ { "pyv", ContentType.Pyv },
+ { "qam", ContentType.Qam },
+ { "qbo", ContentType.Qbo },
+ { "qfx", ContentType.Qfx },
+ { "qps", ContentType.Qps },
+ { "qt", ContentType.Qt },
+ { "qwd", ContentType.Qwd },
+ { "qwt", ContentType.Qwt },
+ { "qxb", ContentType.Qxb },
+ { "qxd", ContentType.Qxd },
+ { "qxl", ContentType.Qxl },
+ { "qxt", ContentType.Qxt },
+ { "ra", ContentType.Ra },
+ { "ram", ContentType.Ram },
+ { "raml", ContentType.Raml },
+ { "rapd", ContentType.Rapd },
+ { "rar", ContentType.Rar },
+ { "ras", ContentType.Ras },
+ { "rdf", ContentType.Rdf },
+ { "rdz", ContentType.Rdz },
+ { "relo", ContentType.Relo },
+ { "rep", ContentType.Rep },
+ { "res", ContentType.Res },
+ { "rgb", ContentType.Rgb },
+ { "rif", ContentType.Rif },
+ { "rip", ContentType.Rip },
+ { "ris", ContentType.Ris },
+ { "rl", ContentType.Rl },
+ { "rlc", ContentType.Rlc },
+ { "rld", ContentType.Rld },
+ { "rm", ContentType.Rm },
+ { "rmi", ContentType.Rmi },
+ { "rmp", ContentType.Rmp },
+ { "rms", ContentType.Rms },
+ { "rmvb", ContentType.Rmvb },
+ { "rnc", ContentType.Rnc },
+ { "rng", ContentType.Rng },
+ { "roa", ContentType.Roa },
+ { "roff", ContentType.Roff },
+ { "rp9", ContentType.Rp9 },
+ { "rpm", ContentType.Rpm },
+ { "rpss", ContentType.Rpss },
+ { "rpst", ContentType.Rpst },
+ { "rq", ContentType.Rq },
+ { "rs", ContentType.Rs },
+ { "rsat", ContentType.Rsat },
+ { "rsd", ContentType.Rsd },
+ { "rsheet", ContentType.Rsheet },
+ { "rss", ContentType.Rss },
+ { "rtf", ContentType.Rtf },
+ { "rtx", ContentType.Rtx },
+ { "run", ContentType.Run },
+ { "rusd", ContentType.Rusd },
+ { "s", ContentType.S },
+ { "s3m", ContentType.S3m },
+ { "saf", ContentType.Saf },
+ { "sass", ContentType.Sass },
+ { "sbml", ContentType.Sbml },
+ { "sc", ContentType.Sc },
+ { "scd", ContentType.Scd },
+ { "scm", ContentType.Scm },
+ { "scq", ContentType.Scq },
+ { "scs", ContentType.Scs },
+ { "scss", ContentType.Scss },
+ { "scurl", ContentType.Scurl },
+ { "sda", ContentType.Sda },
+ { "sdc", ContentType.Sdc },
+ { "sdd", ContentType.Sdd },
+ { "sdkd", ContentType.Sdkd },
+ { "sdkm", ContentType.Sdkm },
+ { "sdp", ContentType.Sdp },
+ { "sdw", ContentType.Sdw },
+ { "sea", ContentType.Sea },
+ { "see", ContentType.See },
+ { "seed", ContentType.Seed },
+ { "sema", ContentType.Sema },
+ { "semd", ContentType.Semd },
+ { "semf", ContentType.Semf },
+ { "senmlx", ContentType.Senmlx },
+ { "sensmlx", ContentType.Sensmlx },
+ { "ser", ContentType.Ser },
+ { "setpay", ContentType.Setpay },
+ { "setreg", ContentType.Setreg },
+ { "sfs", ContentType.Sfs },
+ { "sfv", ContentType.Sfv },
+ { "sgi", ContentType.Sgi },
+ { "sgl", ContentType.Sgl },
+ { "sgm", ContentType.Sgm },
+ { "sgml", ContentType.Sgml },
+ { "sh", ContentType.Sh },
+ { "shar", ContentType.Shar },
+ { "shex", ContentType.Shex },
+ { "shf", ContentType.Shf },
+ { "shtml", ContentType.Shtml },
+ { "sid", ContentType.Sid },
+ { "sieve", ContentType.Sieve },
+ { "sig", ContentType.Sig },
+ { "sil", ContentType.Sil },
+ { "silo", ContentType.Silo },
+ { "sis", ContentType.Sis },
+ { "sisx", ContentType.Sisx },
+ { "sit", ContentType.Sit },
+ { "sitx", ContentType.Sitx },
+ { "siv", ContentType.Siv },
+ { "skd", ContentType.Skd },
+ { "skm", ContentType.Skm },
+ { "skp", ContentType.Skp },
+ { "skt", ContentType.Skt },
+ { "sldm", ContentType.Sldm },
+ { "sldx", ContentType.Sldx },
+ { "slim", ContentType.Slim },
+ { "slm", ContentType.Slm },
+ { "sls", ContentType.Sls },
+ { "slt", ContentType.Slt },
+ { "sm", ContentType.Sm },
+ { "smf", ContentType.Smf },
+ { "smi", ContentType.Smi },
+ { "smil", ContentType.Smil },
+ { "smv", ContentType.Smv },
+ { "smzip", ContentType.Smzip },
+ { "snd", ContentType.Snd },
+ { "snf", ContentType.Snf },
+ { "so", ContentType.So },
+ { "spc", ContentType.Spc },
+ { "spdx", ContentType.Spdx },
+ { "spf", ContentType.Spf },
+ { "spl", ContentType.Spl },
+ { "spot", ContentType.Spot },
+ { "spp", ContentType.Spp },
+ { "spq", ContentType.Spq },
+ { "spx", ContentType.Spx },
+ { "sql", ContentType.Sql },
+ { "src", ContentType.Src },
+ { "srt", ContentType.Srt },
+ { "sru", ContentType.Sru },
+ { "srx", ContentType.Srx },
+ { "ssdl", ContentType.Ssdl },
+ { "sse", ContentType.Sse },
+ { "ssf", ContentType.Ssf },
+ { "ssml", ContentType.Ssml },
+ { "st", ContentType.St },
+ { "stc", ContentType.Stc },
+ { "std", ContentType.Std },
+ { "stf", ContentType.Stf },
+ { "sti", ContentType.Sti },
+ { "stk", ContentType.Stk },
+ { "stl", ContentType.Stl },
+ { "stpx", ContentType.Stpx },
+ { "stpxz", ContentType.Stpxz },
+ { "stpz", ContentType.Stpz },
+ { "str", ContentType.Str },
+ { "stw", ContentType.Stw },
+ { "styl", ContentType.Styl },
+ { "stylus", ContentType.Stylus },
+ { "sub", ContentType.Sub },
+ { "sus", ContentType.Sus },
+ { "susp", ContentType.Susp },
+ { "sv4cpio", ContentType.Sv4cpio },
+ { "sv4crc", ContentType.Sv4crc },
+ { "svc", ContentType.Svc },
+ { "svd", ContentType.Svd },
+ { "svg", ContentType.Svg },
+ { "svgz", ContentType.Svgz },
+ { "swa", ContentType.Swa },
+ { "swf", ContentType.Swf },
+ { "swi", ContentType.Swi },
+ { "swidtag", ContentType.Swidtag },
+ { "sxc", ContentType.Sxc },
+ { "sxd", ContentType.Sxd },
+ { "sxg", ContentType.Sxg },
+ { "sxi", ContentType.Sxi },
+ { "sxm", ContentType.Sxm },
+ { "sxw", ContentType.Sxw },
+ { "t", ContentType.T },
+ { "t3", ContentType.T3 },
+ { "t38", ContentType.T38 },
+ { "taglet", ContentType.Taglet },
+ { "tao", ContentType.Tao },
+ { "tap", ContentType.Tap },
+ { "tar", ContentType.Tar },
+ { "tcap", ContentType.Tcap },
+ { "tcl", ContentType.Tcl },
+ { "td", ContentType.Td },
+ { "teacher", ContentType.Teacher },
+ { "tei", ContentType.Tei },
+ { "tex", ContentType.Tex },
+ { "texi", ContentType.Texi },
+ { "texinfo", ContentType.Texinfo },
+ { "text", ContentType.Text },
+ { "tfi", ContentType.Tfi },
+ { "tfm", ContentType.Tfm },
+ { "tfx", ContentType.Tfx },
+ { "tga", ContentType.Tga },
+ { "thmx", ContentType.Thmx },
+ { "tif", ContentType.Tif },
+ { "tiff", ContentType.Tiff },
+ { "tk", ContentType.Tk },
+ { "tmo", ContentType.Tmo },
+ { "toml", ContentType.Toml },
+ { "torrent", ContentType.Torrent },
+ { "tpl", ContentType.Tpl },
+ { "tpt", ContentType.Tpt },
+ { "tr", ContentType.Tr },
+ { "tra", ContentType.Tra },
+ { "trig", ContentType.Trig },
+ { "trm", ContentType.Trm },
+ { "ts", ContentType.Ts },
+ { "tsd", ContentType.Tsd },
+ { "tsv", ContentType.Tsv },
+ { "ttc", ContentType.Ttc },
+ { "ttf", ContentType.Ttf },
+ { "ttl", ContentType.Ttl },
+ { "ttml", ContentType.Ttml },
+ { "twd", ContentType.Twd },
+ { "twds", ContentType.Twds },
+ { "txd", ContentType.Txd },
+ { "txf", ContentType.Txf },
+ { "txt", ContentType.Txt },
+ { "u32", ContentType.U32 },
+ { "u8dsn", ContentType.U8dsn },
+ { "u8hdr", ContentType.U8hdr },
+ { "u8mdn", ContentType.U8mdn },
+ { "u8msg", ContentType.U8msg },
+ { "ubj", ContentType.Ubj },
+ { "udeb", ContentType.Udeb },
+ { "ufd", ContentType.Ufd },
+ { "ufdl", ContentType.Ufdl },
+ { "ulx", ContentType.Ulx },
+ { "umj", ContentType.Umj },
+ { "unityweb", ContentType.Unityweb },
+ { "uoml", ContentType.Uoml },
+ { "uri", ContentType.Uri },
+ { "uris", ContentType.Uris },
+ { "urls", ContentType.Urls },
+ { "usdz", ContentType.Usdz },
+ { "ustar", ContentType.Ustar },
+ { "utz", ContentType.Utz },
+ { "uu", ContentType.Uu },
+ { "uva", ContentType.Uva },
+ { "uvd", ContentType.Uvd },
+ { "uvf", ContentType.Uvf },
+ { "uvg", ContentType.Uvg },
+ { "uvh", ContentType.Uvh },
+ { "uvi", ContentType.Uvi },
+ { "uvm", ContentType.Uvm },
+ { "uvp", ContentType.Uvp },
+ { "uvs", ContentType.Uvs },
+ { "uvt", ContentType.Uvt },
+ { "uvu", ContentType.Uvu },
+ { "uvv", ContentType.Uvv },
+ { "uvva", ContentType.Uvva },
+ { "uvvd", ContentType.Uvvd },
+ { "uvvf", ContentType.Uvvf },
+ { "uvvg", ContentType.Uvvg },
+ { "uvvh", ContentType.Uvvh },
+ { "uvvi", ContentType.Uvvi },
+ { "uvvm", ContentType.Uvvm },
+ { "uvvp", ContentType.Uvvp },
+ { "uvvs", ContentType.Uvvs },
+ { "uvvt", ContentType.Uvvt },
+ { "uvvu", ContentType.Uvvu },
+ { "uvvv", ContentType.Uvvv },
+ { "uvvx", ContentType.Uvvx },
+ { "uvvz", ContentType.Uvvz },
+ { "uvx", ContentType.Uvx },
+ { "uvz", ContentType.Uvz },
+ { "vbox", ContentType.Vbox },
+ { "vcard", ContentType.Vcard },
+ { "vcd", ContentType.Vcd },
+ { "vcf", ContentType.Vcf },
+ { "vcg", ContentType.Vcg },
+ { "vcs", ContentType.Vcs },
+ { "vcx", ContentType.Vcx },
+ { "vdi", ContentType.Vdi },
+ { "vds", ContentType.Vds },
+ { "vhd", ContentType.Vhd },
+ { "vis", ContentType.Vis },
+ { "viv", ContentType.Viv },
+ { "vmdk", ContentType.Vmdk },
+ { "vob", ContentType.Vob },
+ { "vor", ContentType.Vor },
+ { "vox", ContentType.Vox },
+ { "vrml", ContentType.Vrml },
+ { "vsd", ContentType.Vsd },
+ { "vsf", ContentType.Vsf },
+ { "vss", ContentType.Vss },
+ { "vst", ContentType.Vst },
+ { "vsw", ContentType.Vsw },
+ { "vtf", ContentType.Vtf },
+ { "vtt", ContentType.Vtt },
+ { "vtu", ContentType.Vtu },
+ { "vxml", ContentType.Vxml },
+ { "w3d", ContentType.W3d },
+ { "wad", ContentType.Wad },
+ { "wadl", ContentType.Wadl },
+ { "war", ContentType.War },
+ { "wasm", ContentType.Wasm },
+ { "wav", ContentType.Wav },
+ { "wax", ContentType.Wax },
+ { "wbmp", ContentType.Wbmp },
+ { "wbs", ContentType.Wbs },
+ { "wbxml", ContentType.Wbxml },
+ { "wcm", ContentType.Wcm },
+ { "wdb", ContentType.Wdb },
+ { "wdp", ContentType.Wdp },
+ { "weba", ContentType.Weba },
+ { "webapp", ContentType.Webapp },
+ { "webm", ContentType.Webm },
+ { "webp", ContentType.Webp },
+ { "wg", ContentType.Wg },
+ { "wgt", ContentType.Wgt },
+ { "wks", ContentType.Wks },
+ { "wm", ContentType.Wm },
+ { "wma", ContentType.Wma },
+ { "wmd", ContentType.Wmd },
+ { "wmf", ContentType.Wmf },
+ { "wml", ContentType.Wml },
+ { "wmlc", ContentType.Wmlc },
+ { "wmls", ContentType.Wmls },
+ { "wmlsc", ContentType.Wmlsc },
+ { "wmv", ContentType.Wmv },
+ { "wmx", ContentType.Wmx },
+ { "wmz", ContentType.Wmz },
+ { "woff", ContentType.Woff },
+ { "woff2", ContentType.Woff2 },
+ { "wpd", ContentType.Wpd },
+ { "wpl", ContentType.Wpl },
+ { "wps", ContentType.Wps },
+ { "wqd", ContentType.Wqd },
+ { "wri", ContentType.Wri },
+ { "wrl", ContentType.Wrl },
+ { "wsc", ContentType.Wsc },
+ { "wsdl", ContentType.Wsdl },
+ { "wspolicy", ContentType.Wspolicy },
+ { "wtb", ContentType.Wtb },
+ { "wvx", ContentType.Wvx },
+ { "x32", ContentType.X32 },
+ { "x3d", ContentType.X3d },
+ { "x3db", ContentType.X3db },
+ { "x3dbz", ContentType.X3dbz },
+ { "x3dv", ContentType.X3dv },
+ { "x3dvz", ContentType.X3dvz },
+ { "x3dz", ContentType.X3dz },
+ { "xaml", ContentType.Xaml },
+ { "xap", ContentType.Xap },
+ { "xar", ContentType.Xar },
+ { "xav", ContentType.Xav },
+ { "xbap", ContentType.Xbap },
+ { "xbd", ContentType.Xbd },
+ { "xbm", ContentType.Xbm },
+ { "xca", ContentType.Xca },
+ { "xcs", ContentType.Xcs },
+ { "xdf", ContentType.Xdf },
+ { "xdm", ContentType.Xdm },
+ { "xdp", ContentType.Xdp },
+ { "xdssc", ContentType.Xdssc },
+ { "xdw", ContentType.Xdw },
+ { "xel", ContentType.Xel },
+ { "xenc", ContentType.Xenc },
+ { "xer", ContentType.Xer },
+ { "xfdf", ContentType.Xfdf },
+ { "xfdl", ContentType.Xfdl },
+ { "xht", ContentType.Xht },
+ { "xhtml", ContentType.Xhtml },
+ { "xhvml", ContentType.Xhvml },
+ { "xif", ContentType.Xif },
+ { "xla", ContentType.Xla },
+ { "xlam", ContentType.Xlam },
+ { "xlc", ContentType.Xlc },
+ { "xlf", ContentType.Xlf },
+ { "xlm", ContentType.Xlm },
+ { "xls", ContentType.Xls },
+ { "xlsb", ContentType.Xlsb },
+ { "xlsm", ContentType.Xlsm },
+ { "xlsx", ContentType.Xlsx },
+ { "xlt", ContentType.Xlt },
+ { "xltm", ContentType.Xltm },
+ { "xltx", ContentType.Xltx },
+ { "xlw", ContentType.Xlw },
+ { "xm", ContentType.Xm },
+ { "xml", ContentType.Xml },
+ { "xns", ContentType.Xns },
+ { "xo", ContentType.Xo },
+ { "xop", ContentType.Xop },
+ { "xpi", ContentType.Xpi },
+ { "xpl", ContentType.Xpl },
+ { "xpm", ContentType.Xpm },
+ { "xpr", ContentType.Xpr },
+ { "xps", ContentType.Xps },
+ { "xpw", ContentType.Xpw },
+ { "xpx", ContentType.Xpx },
+ { "xsd", ContentType.Xsd },
+ { "xsl", ContentType.Xsl },
+ { "xslt", ContentType.Xslt },
+ { "xsm", ContentType.Xsm },
+ { "xspf", ContentType.Xspf },
+ { "xul", ContentType.Xul },
+ { "xvm", ContentType.Xvm },
+ { "xvml", ContentType.Xvml },
+ { "xwd", ContentType.Xwd },
+ { "xyz", ContentType.Xyz },
+ { "xz", ContentType.Xz },
+ { "yaml", ContentType.Yaml },
+ { "yang", ContentType.Yang },
+ { "yin", ContentType.Yin },
+ { "yml", ContentType.Yml },
+ { "ymp", ContentType.Ymp },
+ { "z1", ContentType.Z1 },
+ { "z2", ContentType.Z2 },
+ { "z3", ContentType.Z3 },
+ { "z4", ContentType.Z4 },
+ { "z5", ContentType.Z5 },
+ { "z6", ContentType.Z6 },
+ { "z7", ContentType.Z7 },
+ { "z8", ContentType.Z8 },
+ { "zaz", ContentType.Zaz },
+ { "zip", ContentType.Zip },
+ { "zir", ContentType.Zir },
+ { "zirz", ContentType.Zirz },
+ { "zmm", ContentType.Zmm },
+ };
+ private static readonly IReadOnlyDictionary<string, ContentType> MimeToCt = new Dictionary<string, ContentType>()
+ {
+ { "application/x-www-form-urlencoded", ContentType.UrlEncoded },
+ { "multipart/form-data", ContentType.MultiPart },
+ { "audio/x-aac", ContentType.Aac },
+ { "application/x-authorware-map", ContentType.Aam },
+ { "application/x-authorware-seg", ContentType.Aas },
+ { "application/x-abiword", ContentType.Abw },
+ { "application/pkix-attr-cert", ContentType.Ac },
+ { "application/vnd.americandynamics.acc", ContentType.Acc },
+ { "application/x-ace-compressed", ContentType.Ace },
+ { "application/vnd.acucobol", ContentType.Acu },
+ { "application/vnd.acucorp", ContentType.Acutc },
+ { "audio/adpcm", ContentType.Adp },
+ { "application/vnd.audiograph", ContentType.Aep },
+ { "application/vnd.ibm.modcap", ContentType.Afp },
+ { "application/vnd.ahead.space", ContentType.Ahead },
+ { "audio/x-aiff", ContentType.Aiff },
+ { "application/vnd.adobe.air-application-installer-package+zip", ContentType.Air },
+ { "application/vnd.dvb.ait", ContentType.Ait },
+ { "application/vnd.amiga.ami", ContentType.Ami },
+ { "audio/amr", ContentType.Amr },
+ { "application/vnd.android.package-archive", ContentType.Apk },
+ { "image/apng", ContentType.Apng },
+ { "application/vnd.lotus-approach", ContentType.Apr },
+ { "application/x-freearc", ContentType.Arc },
+ { "application/x-arj", ContentType.Arj },
+ { "video/x-ms-asf", ContentType.Asf },
+ { "text/x-asm", ContentType.Asm },
+ { "application/vnd.accpac.simply.aso", ContentType.Aso },
+ { "application/atom+xml", ContentType.Atom },
+ { "application/atomcat+xml", ContentType.Atomcat },
+ { "application/atomsvc+xml", ContentType.Atomsvc },
+ { "application/vnd.antix.game-component", ContentType.Atx },
+ { "audio/basic", ContentType.Au },
+ { "video/x-msvideo", ContentType.Avi },
+ { "image/avif", ContentType.Avif },
+ { "application/applixware", ContentType.Aw },
+ { "application/vnd.airzip.filesecure.azf", ContentType.Azf },
+ { "application/vnd.airzip.filesecure.azs", ContentType.Azs },
+ { "image/vnd.airzip.accelerator.azv", ContentType.Azv },
+ { "application/vnd.amazon.ebook", ContentType.Azw },
+ { "image/vnd.pco.b16", ContentType.B16 },
+ { "application/x-msdownload", ContentType.Bat },
+ { "application/x-bcpio", ContentType.Bcpio },
+ { "application/x-font-bdf", ContentType.Bdf },
+ { "application/vnd.syncml.dm+wbxml", ContentType.Bdm },
+ { "application/bdoc", ContentType.Bdoc },
+ { "application/vnd.realvnc.bed", ContentType.Bed },
+ { "application/vnd.fujitsu.oasysprs", ContentType.Bh2 },
+ { "application/octet-stream", ContentType.Binary },
+ { "application/x-blorb", ContentType.Blorb },
+ { "application/vnd.bmi", ContentType.Bmi },
+ { "application/vnd.balsamiq.bmml+xml", ContentType.Bmml },
+ { "image/bmp", ContentType.Bmp },
+ { "application/vnd.previewsystems.box", ContentType.Box },
+ { "application/x-bzip2", ContentType.Boz },
+ { "model/vnd.valve.source.compiled-map", ContentType.Bsp },
+ { "image/prs.btif", ContentType.Btif },
+ { "application/x-bzip", ContentType.Bz },
+ { "text/x-c", ContentType.C },
+ { "application/vnd.cluetrust.cartomobile-config", ContentType.C11amc },
+ { "application/vnd.cluetrust.cartomobile-config-pkg", ContentType.C11amz },
+ { "application/vnd.clonk.c4group", ContentType.C4d },
+ { "application/vnd.ms-cab-compressed", ContentType.Cab },
+ { "audio/x-caf", ContentType.Caf },
+ { "application/vnd.curl.car", ContentType.Car },
+ { "application/vnd.ms-pki.seccat", ContentType.Cat },
+ { "application/x-cbr", ContentType.Cb7 },
+ { "application/x-cocoa", ContentType.Cco },
+ { "application/ccxml+xml", ContentType.Ccxml },
+ { "application/vnd.contact.cmsg", ContentType.Cdbcmsg },
+ { "application/x-netcdf", ContentType.Cdf },
+ { "application/cdfx+xml", ContentType.Cdfx },
+ { "application/vnd.mediastation.cdkey", ContentType.Cdkey },
+ { "application/cdmi-capability", ContentType.Cdmia },
+ { "application/cdmi-container", ContentType.Cdmic },
+ { "application/cdmi-domain", ContentType.Cdmid },
+ { "application/cdmi-object", ContentType.Cdmio },
+ { "application/cdmi-queue", ContentType.Cdmiq },
+ { "chemical/x-cdx", ContentType.Cdx },
+ { "application/vnd.chemdraw+xml", ContentType.Cdxml },
+ { "application/vnd.cinderella", ContentType.Cdy },
+ { "application/pkix-cert", ContentType.Cer },
+ { "application/x-cfs-compressed", ContentType.Cfs },
+ { "image/cgm", ContentType.Cgm },
+ { "application/x-chat", ContentType.Chat },
+ { "application/vnd.ms-htmlhelp", ContentType.Chm },
+ { "application/vnd.kde.kchart", ContentType.Chrt },
+ { "chemical/x-cif", ContentType.Cif },
+ { "application/vnd.anser-web-certificate-issue-initiation", ContentType.Cii },
+ { "application/vnd.ms-artgalry", ContentType.Cil },
+ { "application/node", ContentType.Cjs },
+ { "application/vnd.claymore", ContentType.Cla },
+ { "application/vnd.crick.clicker.keyboard", ContentType.Clkk },
+ { "application/vnd.crick.clicker.palette", ContentType.Clkp },
+ { "application/vnd.crick.clicker.template", ContentType.Clkt },
+ { "application/vnd.crick.clicker.wordbank", ContentType.Clkw },
+ { "application/vnd.crick.clicker", ContentType.Clkx },
+ { "application/x-msclip", ContentType.Clp },
+ { "application/vnd.cosmocaller", ContentType.Cmc },
+ { "chemical/x-cmdf", ContentType.Cmdf },
+ { "chemical/x-cml", ContentType.Cml },
+ { "application/vnd.yellowriver-custom-menu", ContentType.Cmp },
+ { "image/x-cmx", ContentType.Cmx },
+ { "application/vnd.rim.cod", ContentType.Cod },
+ { "text/coffeescript", ContentType.Coffee },
+ { "application/x-cpio", ContentType.Cpio },
+ { "application/mac-compactpro", ContentType.Cpt },
+ { "application/x-mscardfile", ContentType.Crd },
+ { "application/pkix-crl", ContentType.Crl },
+ { "application/x-x509-ca-cert", ContentType.Crt },
+ { "application/x-chrome-extension", ContentType.Crx },
+ { "application/x-csh", ContentType.Csh },
+ { "application/vnd.citationstyles.style+xml", ContentType.Csl },
+ { "chemical/x-csml", ContentType.Csml },
+ { "application/vnd.commonspace", ContentType.Csp },
+ { "text/css", ContentType.Css },
+ { "text/csv", ContentType.Csv },
+ { "application/cu-seeme", ContentType.Cu },
+ { "text/vnd.curl", ContentType.Curl },
+ { "application/prs.cww", ContentType.Cww },
+ { "model/vnd.collada+xml", ContentType.Dae },
+ { "application/vnd.mobius.daf", ContentType.Daf },
+ { "application/vnd.dart", ContentType.Dart },
+ { "application/davmount+xml", ContentType.Davmount },
+ { "application/vnd.dbf", ContentType.Dbf },
+ { "application/docbook+xml", ContentType.Dbk },
+ { "application/x-director", ContentType.Dcr },
+ { "text/vnd.curl.dcurl", ContentType.Dcurl },
+ { "application/vnd.oma.dd2+xml", ContentType.Dd2 },
+ { "application/vnd.fujixerox.ddd", ContentType.Ddd },
+ { "application/vnd.syncml.dmddf+xml", ContentType.Ddf },
+ { "image/vnd.ms-dds", ContentType.Dds },
+ { "application/vnd.dreamfactory", ContentType.Dfac },
+ { "application/x-dgc-compressed", ContentType.Dgc },
+ { "application/vnd.mobius.dis", ContentType.Dis },
+ { "image/vnd.djvu", ContentType.Djvu },
+ { "application/vnd.dna", ContentType.Dna },
+ { "application/msword", ContentType.Doc },
+ { "application/vnd.ms-word.document.macroenabled.12", ContentType.Docm },
+ { "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ContentType.Docx },
+ { "application/vnd.openxmlformats-officedocument.wordprocessingml.template", ContentType.Dotx },
+ { "application/vnd.osgi.dp", ContentType.Dp },
+ { "application/vnd.dpgraph", ContentType.Dpg },
+ { "audio/vnd.dra", ContentType.Dra },
+ { "image/dicom-rle", ContentType.Drle },
+ { "text/prs.lines.tag", ContentType.Dsc },
+ { "application/dssc+der", ContentType.Dssc },
+ { "application/x-dtbook+xml", ContentType.Dtb },
+ { "application/xml-dtd", ContentType.Dtd },
+ { "audio/vnd.dts", ContentType.Dts },
+ { "audio/vnd.dts.hd", ContentType.Dtshd },
+ { "video/vnd.dvb.file", ContentType.Dvb },
+ { "application/x-dvi", ContentType.Dvi },
+ { "application/atsc-dwd+xml", ContentType.Dwd },
+ { "model/vnd.dwf", ContentType.Dwf },
+ { "image/vnd.dwg", ContentType.Dwg },
+ { "image/vnd.dxf", ContentType.Dxf },
+ { "application/vnd.spotfire.dxp", ContentType.Dxp },
+ { "application/ecmascript", ContentType.Ecma },
+ { "application/vnd.novadigm.edm", ContentType.Edm },
+ { "application/vnd.novadigm.edx", ContentType.Edx },
+ { "application/vnd.picsel", ContentType.Efif },
+ { "application/vnd.pg.osasli", ContentType.Ei6 },
+ { "application/emma+xml", ContentType.Emma },
+ { "audio/vnd.digital-winds", ContentType.Eol },
+ { "application/vnd.ms-fontobject", ContentType.Eot },
+ { "application/epub+zip", ContentType.Epub },
+ { "application/vnd.osgi.subsystem", ContentType.Esa },
+ { "application/vnd.epson.esf", ContentType.Esf },
+ { "application/vnd.eszigno3+xml", ContentType.Et3 },
+ { "text/x-setext", ContentType.Etx },
+ { "application/x-eva", ContentType.Eva },
+ { "application/x-envoy", ContentType.Evy },
+ { "application/exi", ContentType.Exi },
+ { "application/express", ContentType.Exp },
+ { "image/aces", ContentType.Exr },
+ { "application/vnd.novadigm.ext", ContentType.Ext },
+ { "application/andrew-inset", ContentType.Ez },
+ { "application/vnd.ezpix-album", ContentType.Ez2 },
+ { "application/vnd.ezpix-package", ContentType.Ez3 },
+ { "video/x-f4v", ContentType.F4v },
+ { "text/x-fortran", ContentType.Fortran },
+ { "image/vnd.fastbidsheet", ContentType.Fbs },
+ { "application/vnd.adobe.formscentral.fcdt", ContentType.Fcdt },
+ { "application/vnd.isac.fcs", ContentType.Fcs },
+ { "application/vnd.fdf", ContentType.Fdf },
+ { "application/fdt+xml", ContentType.Fdt },
+ { "application/vnd.fujitsu.oasysgp", ContentType.Fg5 },
+ { "image/x-freehand", ContentType.Fh },
+ { "application/x-xfig", ContentType.Fig },
+ { "image/fits", ContentType.Fits },
+ { "audio/x-flac", ContentType.Flac },
+ { "video/x-fli", ContentType.Fli },
+ { "application/vnd.micrografx.flo", ContentType.Flo },
+ { "video/x-flv", ContentType.Flv },
+ { "application/vnd.kde.kivio", ContentType.Flw },
+ { "text/vnd.fmi.flexstor", ContentType.Flx },
+ { "text/vnd.fly", ContentType.Fly },
+ { "application/vnd.frogans.fnc", ContentType.Fnc },
+ { "application/vnd.software602.filler.form+xml", ContentType.Fo },
+ { "image/vnd.fpx", ContentType.Fpx },
+ { "application/vnd.framemaker", ContentType.Frame },
+ { "application/vnd.fsc.weblaunch", ContentType.Fsc },
+ { "image/vnd.fst", ContentType.Fst },
+ { "application/vnd.fluxtime.clip", ContentType.Ftc },
+ { "application/vnd.anser-web-funds-transfer-initiation", ContentType.Fti },
+ { "video/vnd.fvt", ContentType.Fvt },
+ { "application/vnd.adobe.fxp", ContentType.Fxp },
+ { "application/vnd.fuzzysheet", ContentType.Fzs },
+ { "application/vnd.geoplan", ContentType.G2w },
+ { "image/g3fax", ContentType.G3 },
+ { "application/vnd.geospace", ContentType.G3w },
+ { "application/vnd.groove-account", ContentType.Gac },
+ { "application/x-tads", ContentType.Gam },
+ { "application/rpki-ghostbusters", ContentType.Gbr },
+ { "application/x-gca-compressed", ContentType.Gca },
+ { "model/vnd.gdl", ContentType.Gdl },
+ { "application/vnd.google-apps.document", ContentType.Gdoc },
+ { "application/vnd.dynageo", ContentType.Geo },
+ { "application/geo+json", ContentType.Geojson },
+ { "application/vnd.geometry-explorer", ContentType.Gex },
+ { "application/vnd.geogebra.file", ContentType.Ggb },
+ { "application/vnd.geogebra.tool", ContentType.Ggt },
+ { "application/vnd.groove-help", ContentType.Ghf },
+ { "image/gif", ContentType.Gif },
+ { "application/vnd.groove-identity-message", ContentType.Gim },
+ { "model/gltf-binary", ContentType.Glb },
+ { "model/gltf+json", ContentType.Gltf },
+ { "application/gml+xml", ContentType.Gml },
+ { "application/vnd.gmx", ContentType.Gmx },
+ { "application/x-gnumeric", ContentType.Gnumeric },
+ { "application/vnd.flographit", ContentType.Gph },
+ { "application/gpx+xml", ContentType.Gpx },
+ { "application/vnd.grafeq", ContentType.Gqf },
+ { "application/srgs", ContentType.Gram },
+ { "application/x-gramps-xml", ContentType.Gramps },
+ { "application/vnd.groove-injector", ContentType.Grv },
+ { "application/srgs+xml", ContentType.Grxml },
+ { "application/x-font-ghostscript", ContentType.Gsf },
+ { "application/vnd.google-apps.spreadsheet", ContentType.Gsheet },
+ { "application/vnd.google-apps.presentation", ContentType.Gslides },
+ { "application/x-gtar", ContentType.Gtar },
+ { "application/vnd.groove-tool-message", ContentType.Gtm },
+ { "model/vnd.gtw", ContentType.Gtw },
+ { "text/vnd.graphviz", ContentType.Gv },
+ { "application/gxf", ContentType.Gxf },
+ { "application/vnd.geonext", ContentType.Gxt },
+ { "application/gzip", ContentType.Gz },
+ { "video/h261", ContentType.H261 },
+ { "video/h263", ContentType.H263 },
+ { "video/h264", ContentType.H264 },
+ { "application/vnd.hal+xml", ContentType.Hal },
+ { "application/vnd.hbci", ContentType.Hbci },
+ { "text/x-handlebars-template", ContentType.Hbs },
+ { "application/x-virtualbox-hdd", ContentType.Hdd },
+ { "application/x-hdf", ContentType.Hdf },
+ { "image/heic", ContentType.Heic },
+ { "image/heic-sequence", ContentType.Heics },
+ { "image/heif", ContentType.Heif },
+ { "image/heif-sequence", ContentType.Heifs },
+ { "image/hej2k", ContentType.Hej2 },
+ { "application/atsc-held+xml", ContentType.Held },
+ { "application/hjson", ContentType.Hjson },
+ { "application/winhlp", ContentType.Hlp },
+ { "application/vnd.hp-hpgl", ContentType.Hpgl },
+ { "application/vnd.hp-hpid", ContentType.Hpid },
+ { "application/vnd.hp-hps", ContentType.Hps },
+ { "application/mac-binhex40", ContentType.Hqx },
+ { "image/hsj2", ContentType.Hsj2 },
+ { "application/vnd.kenameaapp", ContentType.Htke },
+ { "text/html", ContentType.Html },
+ { "application/vnd.yamaha.hv-dic", ContentType.Hvd },
+ { "application/vnd.yamaha.hv-voice", ContentType.Hvp },
+ { "application/vnd.yamaha.hv-script", ContentType.Hvs },
+ { "application/vnd.intergeo", ContentType.I2g },
+ { "application/vnd.iccprofile", ContentType.Icc },
+ { "x-conference/x-cooltalk", ContentType.Ice },
+ { "image/vnd.microsoft.icon", ContentType.Ico },
+ { "image/ief", ContentType.Ief },
+ { "application/vnd.shana.informed.formdata", ContentType.Ifm },
+ { "model/iges", ContentType.Iges },
+ { "application/vnd.igloader", ContentType.Igl },
+ { "application/vnd.insors.igm", ContentType.Igm },
+ { "application/vnd.micrografx.igx", ContentType.Igx },
+ { "application/vnd.shana.informed.interchange", ContentType.Iif },
+ { "application/vnd.accpac.simply.imp", ContentType.Imp },
+ { "application/vnd.ms-ims", ContentType.Ims },
+ { "application/inkml+xml", ContentType.Inkml },
+ { "application/x-install-instructions", ContentType.Install },
+ { "application/vnd.astraea-software.iota", ContentType.Iota },
+ { "application/ipfix", ContentType.Ipfix },
+ { "application/vnd.shana.informed.package", ContentType.Ipk },
+ { "application/vnd.ibm.rights-management", ContentType.Irm },
+ { "application/vnd.irepository.package+xml", ContentType.Irp },
+ { "application/vnd.shana.informed.formtemplate", ContentType.Itp },
+ { "application/its+xml", ContentType.Its },
+ { "application/vnd.immervision-ivp", ContentType.Ivp },
+ { "application/vnd.immervision-ivu", ContentType.Ivu },
+ { "text/vnd.sun.j2me.app-descriptor", ContentType.Jad },
+ { "text/jade", ContentType.Jade },
+ { "application/vnd.jam", ContentType.Jam },
+ { "application/java-archive", ContentType.Jar },
+ { "application/x-java-archive-diff", ContentType.Jardiff },
+ { "text/x-java-source", ContentType.Java },
+ { "image/jphc", ContentType.Jhc },
+ { "application/vnd.jisp", ContentType.Jisp },
+ { "image/jls", ContentType.Jls },
+ { "application/vnd.hp-jlyt", ContentType.Jlt },
+ { "image/x-jng", ContentType.Jng },
+ { "application/x-java-jnlp-file", ContentType.Jnlp },
+ { "application/vnd.joost.joda-archive", ContentType.Joda },
+ { "image/jp2", ContentType.Jp2 },
+ { "image/jpeg", ContentType.Jpeg },
+ { "video/jpm", ContentType.Jpgm },
+ { "video/jpeg", ContentType.Jpgv },
+ { "image/jph", ContentType.Jph },
+ { "image/jpm", ContentType.Jpm },
+ { "image/jpx", ContentType.Jpx },
+ { "application/javascript", ContentType.Javascript },
+ { "application/json", ContentType.Json },
+ { "application/json5", ContentType.Json5 },
+ { "application/ld+json", ContentType.Jsonld },
+ { "application/jsonml+json", ContentType.Jsonml },
+ { "text/jsx", ContentType.Jsx },
+ { "image/jxr", ContentType.Jxr },
+ { "image/jxra", ContentType.Jxra },
+ { "image/jxrs", ContentType.Jxrs },
+ { "image/jxs", ContentType.Jxs },
+ { "image/jxsc", ContentType.Jxsc },
+ { "image/jxsi", ContentType.Jxsi },
+ { "image/jxss", ContentType.Jxss },
+ { "application/vnd.kde.karbon", ContentType.Karbon },
+ { "application/x-keepass2", ContentType.Kdbx },
+ { "application/vnd.apple.keynote", ContentType.Key },
+ { "application/vnd.kde.kformula", ContentType.Kfo },
+ { "application/vnd.kidspiration", ContentType.Kia },
+ { "application/vnd.google-earth.kml+xml", ContentType.Kml },
+ { "application/vnd.google-earth.kmz", ContentType.Kmz },
+ { "application/vnd.kinar", ContentType.Kne },
+ { "application/vnd.kde.kontour", ContentType.Kon },
+ { "application/vnd.kde.kpresenter", ContentType.Kpr },
+ { "application/vnd.ds-keypoint", ContentType.Kpxx },
+ { "application/vnd.kde.kspread", ContentType.Ksp },
+ { "image/ktx", ContentType.Ktx },
+ { "image/ktx2", ContentType.Ktx2 },
+ { "application/vnd.kahootz", ContentType.Ktz },
+ { "application/vnd.kde.kword", ContentType.Kwd },
+ { "application/vnd.las.las+xml", ContentType.Lasxml },
+ { "application/x-latex", ContentType.Latex },
+ { "application/vnd.llamagraphics.life-balance.desktop", ContentType.Lbd },
+ { "application/vnd.llamagraphics.life-balance.exchange+xml", ContentType.Lbe },
+ { "application/vnd.hhe.lesson-player", ContentType.Les },
+ { "text/less", ContentType.Less },
+ { "application/lgr+xml", ContentType.Lgr },
+ { "application/vnd.route66.link66+xml", ContentType.Link66 },
+ { "application/x-ms-shortcut", ContentType.Lnk },
+ { "application/lost+xml", ContentType.Lostxml },
+ { "application/vnd.ms-lrm", ContentType.Lrm },
+ { "application/vnd.frogans.ltf", ContentType.Ltf },
+ { "text/x-lua", ContentType.Lua },
+ { "application/x-lua-bytecode", ContentType.Luac },
+ { "audio/vnd.lucent.voice", ContentType.Lvp },
+ { "application/vnd.lotus-wordpro", ContentType.Lwp },
+ { "application/x-lzh-compressed", ContentType.Lzh },
+ { "audio/mpeg", ContentType.M2a },
+ { "audio/x-mpegurl", ContentType.M3u },
+ { "application/vnd.apple.mpegurl", ContentType.M3u8 },
+ { "audio/mp4", ContentType.M4a },
+ { "video/iso.segment", ContentType.M4s },
+ { "video/vnd.mpegurl", ContentType.M4u },
+ { "video/x-m4v", ContentType.M4v },
+ { "application/mathematica", ContentType.Ma },
+ { "application/mads+xml", ContentType.Mads },
+ { "application/mmt-aei+xml", ContentType.Maei },
+ { "application/vnd.ecowin.chart", ContentType.Mag },
+ { "text/cache-manifest", ContentType.Manifest },
+ { "text/markdown", ContentType.Markdown },
+ { "application/mathml+xml", ContentType.Mathml },
+ { "application/vnd.mobius.mbk", ContentType.Mbk },
+ { "application/mbox", ContentType.Mbox },
+ { "application/vnd.medcalcdata", ContentType.Mc1 },
+ { "application/vnd.mcd", ContentType.Mcd },
+ { "text/vnd.curl.mcurl", ContentType.Mcurl },
+ { "application/x-msaccess", ContentType.Mdb },
+ { "image/vnd.ms-modi", ContentType.Mdi },
+ { "text/mdx", ContentType.Mdx },
+ { "model/mesh", ContentType.Mesh },
+ { "application/metalink4+xml", ContentType.Meta4 },
+ { "application/metalink+xml", ContentType.Metalink },
+ { "application/mets+xml", ContentType.Mets },
+ { "application/vnd.mfmp", ContentType.Mfm },
+ { "application/rpki-manifest", ContentType.Mft },
+ { "application/vnd.osgeo.mapguide.package", ContentType.Mgp },
+ { "application/vnd.proteus.magazine", ContentType.Mgz },
+ { "audio/midi", ContentType.Midi },
+ { "application/x-mie", ContentType.Mie },
+ { "application/vnd.mif", ContentType.Mif },
+ { "message/rfc822", ContentType.Mime },
+ { "video/mj2", ContentType.Mj2 },
+ { "audio/x-matroska", ContentType.Mka },
+ { "text/x-markdown", ContentType.Mkd },
+ { "video/x-matroska", ContentType.Mkv },
+ { "application/vnd.dolby.mlp", ContentType.Mlp },
+ { "application/vnd.chipnuts.karaoke-mmd", ContentType.Mmd },
+ { "application/vnd.smaf", ContentType.Mmf },
+ { "text/mathml", ContentType.Mml },
+ { "image/vnd.fujixerox.edmics-mmr", ContentType.Mmr },
+ { "video/x-mng", ContentType.Mng },
+ { "application/x-msmoney", ContentType.Mny },
+ { "application/mods+xml", ContentType.Mods },
+ { "video/x-sgi-movie", ContentType.Movie },
+ { "application/mp21", ContentType.Mp21 },
+ { "audio/mp3", ContentType.Mp3 },
+ { "video/mp4", ContentType.Mp4 },
+ { "application/mp4", ContentType.Mp4s },
+ { "application/vnd.mophun.certificate", ContentType.Mpc },
+ { "application/dash+xml", ContentType.Mpd },
+ { "video/mpeg", ContentType.Mpeg },
+ { "application/vnd.apple.installer+xml", ContentType.Mpkg },
+ { "application/vnd.blueice.multipass", ContentType.Mpm },
+ { "application/vnd.mophun.application", ContentType.Mpn },
+ { "application/vnd.ms-project", ContentType.Mpt },
+ { "application/vnd.ibm.minipay", ContentType.Mpy },
+ { "application/vnd.mobius.mqy", ContentType.Mqy },
+ { "application/marc", ContentType.Mrc },
+ { "application/marcxml+xml", ContentType.Mrcx },
+ { "application/mediaservercontrol+xml", ContentType.Mscml },
+ { "application/vnd.fdsn.mseed", ContentType.Mseed },
+ { "application/vnd.mseq", ContentType.Mseq },
+ { "application/vnd.epson.msf", ContentType.Msf },
+ { "application/vnd.ms-outlook", ContentType.Msg },
+ { "application/vnd.mobius.msl", ContentType.Msl },
+ { "application/vnd.muvee.style", ContentType.Msty },
+ { "model/mtl", ContentType.Mtl },
+ { "model/vnd.mts", ContentType.Mts },
+ { "application/vnd.musician", ContentType.Mus },
+ { "application/mmt-usd+xml", ContentType.Musd },
+ { "application/vnd.recordare.musicxml+xml", ContentType.Musicxml },
+ { "application/x-msmediaview", ContentType.Mvb },
+ { "application/vnd.mapbox-vector-tile", ContentType.Mvt },
+ { "application/vnd.mfer", ContentType.Mwf },
+ { "application/mxf", ContentType.Mxf },
+ { "application/vnd.recordare.musicxml", ContentType.Mxl },
+ { "audio/mobile-xmf", ContentType.Mxmf },
+ { "application/vnd.triscape.mxs", ContentType.Mxs },
+ { "text/n3", ContentType.N3 },
+ { "application/vnd.wolfram.player", ContentType.Nbp },
+ { "application/x-dtbncx+xml", ContentType.Ncx },
+ { "text/x-nfo", ContentType.Nfo },
+ { "application/vnd.nokia.n-gage.data", ContentType.Ngdat },
+ { "application/vnd.nitf", ContentType.Nitf },
+ { "application/vnd.neurolanguage.nlu", ContentType.Nlu },
+ { "application/vnd.enliven", ContentType.Nml },
+ { "application/vnd.noblenet-directory", ContentType.Nnd },
+ { "application/vnd.noblenet-sealer", ContentType.Nns },
+ { "application/vnd.noblenet-web", ContentType.Nnw },
+ { "image/vnd.net-fpx", ContentType.Npx },
+ { "application/n-quads", ContentType.Nq },
+ { "application/x-conference", ContentType.Nsc },
+ { "application/vnd.lotus-notes", ContentType.Nsf },
+ { "application/n-triples", ContentType.Nt },
+ { "application/vnd.apple.numbers", ContentType.Numbers },
+ { "application/x-nzb", ContentType.Nzb },
+ { "application/vnd.fujitsu.oasys2", ContentType.Oa2 },
+ { "application/vnd.fujitsu.oasys3", ContentType.Oa3 },
+ { "application/vnd.fujitsu.oasys", ContentType.Oas },
+ { "application/x-msbinder", ContentType.Obd },
+ { "application/vnd.openblox.game+xml", ContentType.Obgx },
+ { "application/x-tgif", ContentType.Obj },
+ { "application/oda", ContentType.Oda },
+ { "application/vnd.oasis.opendocument.database", ContentType.Odb },
+ { "application/vnd.oasis.opendocument.chart", ContentType.Odc },
+ { "application/vnd.oasis.opendocument.formula", ContentType.Odf },
+ { "application/vnd.oasis.opendocument.formula-template", ContentType.Odft },
+ { "application/vnd.oasis.opendocument.graphics", ContentType.Odg },
+ { "application/vnd.oasis.opendocument.image", ContentType.Odi },
+ { "application/vnd.oasis.opendocument.text-master", ContentType.Odm },
+ { "application/vnd.oasis.opendocument.presentation", ContentType.Odp },
+ { "application/vnd.oasis.opendocument.spreadsheet", ContentType.Ods },
+ { "application/vnd.oasis.opendocument.text", ContentType.Odt },
+ { "model/vnd.opengex", ContentType.Ogex },
+ { "audio/ogg", ContentType.Ogg },
+ { "video/ogg", ContentType.Ogv },
+ { "application/ogg", ContentType.Ogx },
+ { "application/omdoc+xml", ContentType.Omdoc },
+ { "application/onenote", ContentType.Onetoc },
+ { "application/oebps-package+xml", ContentType.Opf },
+ { "text/x-opml", ContentType.Opml },
+ { "application/vnd.lotus-organizer", ContentType.Org },
+ { "application/vnd.yamaha.openscoreformat", ContentType.Osf },
+ { "application/vnd.yamaha.openscoreformat.osfpvg+xml", ContentType.Osfpvg },
+ { "application/vnd.openstreetmap.data+xml", ContentType.Osm },
+ { "application/vnd.oasis.opendocument.chart-template", ContentType.Otc },
+ { "font/otf", ContentType.Otf },
+ { "application/vnd.oasis.opendocument.graphics-template", ContentType.Otg },
+ { "application/vnd.oasis.opendocument.text-web", ContentType.Oth },
+ { "application/vnd.oasis.opendocument.image-template", ContentType.Oti },
+ { "application/vnd.oasis.opendocument.presentation-template", ContentType.Otp },
+ { "application/vnd.oasis.opendocument.spreadsheet-template", ContentType.Ots },
+ { "application/vnd.oasis.opendocument.text-template", ContentType.Ott },
+ { "application/x-virtualbox-ova", ContentType.Ova },
+ { "application/x-virtualbox-ovf", ContentType.Ovf },
+ { "application/oxps", ContentType.Oxps },
+ { "application/vnd.openofficeorg.extension", ContentType.Oxt },
+ { "text/x-pascal", ContentType.P },
+ { "application/pkcs10", ContentType.P10 },
+ { "application/x-pkcs7-certificates", ContentType.P7b },
+ { "application/pkcs7-mime", ContentType.P7m },
+ { "application/x-pkcs7-certreqresp", ContentType.P7r },
+ { "application/pkcs7-signature", ContentType.P7s },
+ { "application/pkcs8", ContentType.P8 },
+ { "application/x-ns-proxy-autoconfig", ContentType.Pac },
+ { "application/vnd.apple.pages", ContentType.Pages },
+ { "application/vnd.pawaafile", ContentType.Paw },
+ { "application/vnd.powerbuilder6", ContentType.Pbd },
+ { "image/x-portable-bitmap", ContentType.Pbm },
+ { "application/vnd.tcpdump.pcap", ContentType.Pcap },
+ { "application/x-font-pcf", ContentType.Pcf },
+ { "application/vnd.hp-pcl", ContentType.Pcl },
+ { "application/vnd.hp-pclxl", ContentType.Pclxl },
+ { "image/x-pict", ContentType.Pct },
+ { "application/vnd.curl.pcurl", ContentType.Pcurl },
+ { "image/vnd.zbrush.pcx", ContentType.Pcx },
+ { "application/vnd.palm", ContentType.Pdb },
+ { "text/x-processing", ContentType.Pde },
+ { "application/pdf", ContentType.Pdf },
+ { "application/x-font-type1", ContentType.Pfa },
+ { "application/font-tdpfr", ContentType.Pfr },
+ { "application/x-pkcs12", ContentType.Pfx },
+ { "image/x-portable-graymap", ContentType.Pgm },
+ { "application/x-chess-pgn", ContentType.Pgn },
+ { "application/pgp-encrypted", ContentType.Pgp },
+ { "application/x-httpd-php", ContentType.Php },
+ { "application/pkixcmp", ContentType.Pki },
+ { "application/pkix-pkipath", ContentType.Pkipath },
+ { "application/vnd.apple.pkpass", ContentType.Pkpass },
+ { "application/x-perl", ContentType.Pl },
+ { "application/vnd.3gpp.pic-bw-large", ContentType.Plb },
+ { "application/vnd.mobius.plc", ContentType.Plc },
+ { "application/vnd.pocketlearn", ContentType.Plf },
+ { "application/pls+xml", ContentType.Pls },
+ { "application/vnd.ctc-posml", ContentType.Pml },
+ { "image/png", ContentType.Png },
+ { "image/x-portable-anymap", ContentType.Pnm },
+ { "application/vnd.macports.portpkg", ContentType.Portpkg },
+ { "application/vnd.ms-powerpoint.template.macroenabled.12", ContentType.Potm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.template", ContentType.Potx },
+ { "application/vnd.ms-powerpoint.addin.macroenabled.12", ContentType.Ppam },
+ { "application/vnd.cups-ppd", ContentType.Ppd },
+ { "image/x-portable-pixmap", ContentType.Ppm },
+ { "application/vnd.ms-powerpoint.slideshow.macroenabled.12", ContentType.Ppsm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.slideshow", ContentType.Ppsx },
+ { "application/vnd.ms-powerpoint", ContentType.Ppt },
+ { "application/vnd.ms-powerpoint.presentation.macroenabled.12", ContentType.Pptm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.presentation", ContentType.Pptx },
+ { "application/x-mobipocket-ebook", ContentType.Prc },
+ { "application/vnd.lotus-freelance", ContentType.Pre },
+ { "application/pics-rules", ContentType.Prf },
+ { "application/provenance+xml", ContentType.Provx },
+ { "application/postscript", ContentType.Ps },
+ { "application/vnd.3gpp.pic-bw-small", ContentType.Psb },
+ { "image/vnd.adobe.photoshop", ContentType.Psd },
+ { "application/x-font-linux-psf", ContentType.Psf },
+ { "application/pskc+xml", ContentType.Pskcxml },
+ { "image/prs.pti", ContentType.Pti },
+ { "application/vnd.pvi.ptid1", ContentType.Ptid },
+ { "application/x-mspublisher", ContentType.Pub },
+ { "application/vnd.3gpp.pic-bw-var", ContentType.Pvb },
+ { "application/vnd.3m.post-it-notes", ContentType.Pwn },
+ { "audio/vnd.ms-playready.media.pya", ContentType.Pya },
+ { "video/vnd.ms-playready.media.pyv", ContentType.Pyv },
+ { "application/vnd.epson.quickanime", ContentType.Qam },
+ { "application/vnd.intu.qbo", ContentType.Qbo },
+ { "application/vnd.intu.qfx", ContentType.Qfx },
+ { "application/vnd.publishare-delta-tree", ContentType.Qps },
+ { "video/quicktime", ContentType.Qt },
+ { "application/vnd.quark.quarkxpress", ContentType.Qwd },
+ { "audio/x-pn-realaudio", ContentType.Ra },
+ { "application/raml+yaml", ContentType.Raml },
+ { "application/route-apd+xml", ContentType.Rapd },
+ { "application/vnd.rar", ContentType.Rar },
+ { "image/x-cmu-raster", ContentType.Ras },
+ { "application/rdf+xml", ContentType.Rdf },
+ { "application/vnd.data-vision.rdz", ContentType.Rdz },
+ { "application/p2p-overlay+xml", ContentType.Relo },
+ { "application/vnd.businessobjects", ContentType.Rep },
+ { "application/x-dtbresource+xml", ContentType.Res },
+ { "image/x-rgb", ContentType.Rgb },
+ { "application/reginfo+xml", ContentType.Rif },
+ { "audio/vnd.rip", ContentType.Rip },
+ { "application/x-research-info-systems", ContentType.Ris },
+ { "application/resource-lists+xml", ContentType.Rl },
+ { "image/vnd.fujixerox.edmics-rlc", ContentType.Rlc },
+ { "application/resource-lists-diff+xml", ContentType.Rld },
+ { "application/vnd.rn-realmedia", ContentType.Rm },
+ { "audio/x-pn-realaudio-plugin", ContentType.Rmp },
+ { "application/vnd.jcp.javame.midlet-rms", ContentType.Rms },
+ { "application/vnd.rn-realmedia-vbr", ContentType.Rmvb },
+ { "application/relax-ng-compact-syntax", ContentType.Rnc },
+ { "application/rpki-roa", ContentType.Roa },
+ { "text/troff", ContentType.Roff },
+ { "application/vnd.cloanto.rp9", ContentType.Rp9 },
+ { "application/x-redhat-package-manager", ContentType.Rpm },
+ { "application/vnd.nokia.radio-presets", ContentType.Rpss },
+ { "application/vnd.nokia.radio-preset", ContentType.Rpst },
+ { "application/sparql-query", ContentType.Rq },
+ { "application/rls-services+xml", ContentType.Rs },
+ { "application/atsc-rsat+xml", ContentType.Rsat },
+ { "application/rsd+xml", ContentType.Rsd },
+ { "application/urc-ressheet+xml", ContentType.Rsheet },
+ { "application/rss+xml", ContentType.Rss },
+ { "application/rtf", ContentType.Rtf },
+ { "text/richtext", ContentType.Rtx },
+ { "application/x-makeself", ContentType.Run },
+ { "application/route-usd+xml", ContentType.Rusd },
+ { "audio/s3m", ContentType.S3m },
+ { "application/vnd.yamaha.smaf-audio", ContentType.Saf },
+ { "text/x-sass", ContentType.Sass },
+ { "application/sbml+xml", ContentType.Sbml },
+ { "application/vnd.ibm.secure-container", ContentType.Sc },
+ { "application/x-msschedule", ContentType.Scd },
+ { "application/vnd.lotus-screencam", ContentType.Scm },
+ { "application/scvp-cv-request", ContentType.Scq },
+ { "application/scvp-cv-response", ContentType.Scs },
+ { "text/x-scss", ContentType.Scss },
+ { "text/vnd.curl.scurl", ContentType.Scurl },
+ { "application/vnd.stardivision.draw", ContentType.Sda },
+ { "application/vnd.stardivision.calc", ContentType.Sdc },
+ { "application/vnd.stardivision.impress", ContentType.Sdd },
+ { "application/vnd.solent.sdkm+xml", ContentType.Sdkm },
+ { "application/sdp", ContentType.Sdp },
+ { "application/vnd.stardivision.writer", ContentType.Sdw },
+ { "application/x-sea", ContentType.Sea },
+ { "application/vnd.seemail", ContentType.See },
+ { "application/vnd.fdsn.seed", ContentType.Seed },
+ { "application/vnd.sema", ContentType.Sema },
+ { "application/vnd.semd", ContentType.Semd },
+ { "application/vnd.semf", ContentType.Semf },
+ { "application/senml+xml", ContentType.Senmlx },
+ { "application/sensml+xml", ContentType.Sensmlx },
+ { "application/java-serialized-object", ContentType.Ser },
+ { "application/set-payment-initiation", ContentType.Setpay },
+ { "application/set-registration-initiation", ContentType.Setreg },
+ { "application/vnd.spotfire.sfs", ContentType.Sfs },
+ { "text/x-sfv", ContentType.Sfv },
+ { "image/sgi", ContentType.Sgi },
+ { "application/vnd.stardivision.writer-global", ContentType.Sgl },
+ { "text/sgml", ContentType.Sgml },
+ { "application/x-sh", ContentType.Sh },
+ { "application/x-shar", ContentType.Shar },
+ { "text/shex", ContentType.Shex },
+ { "application/shf+xml", ContentType.Shf },
+ { "image/x-mrsid-image", ContentType.Sid },
+ { "application/sieve", ContentType.Sieve },
+ { "application/pgp-signature", ContentType.Sig },
+ { "audio/silk", ContentType.Sil },
+ { "application/vnd.symbian.install", ContentType.Sisx },
+ { "application/x-stuffit", ContentType.Sit },
+ { "application/x-stuffitx", ContentType.Sitx },
+ { "application/vnd.koan", ContentType.Skd },
+ { "application/vnd.ms-powerpoint.slide.macroenabled.12", ContentType.Sldm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.slide", ContentType.Sldx },
+ { "text/slim", ContentType.Slim },
+ { "application/route-s-tsid+xml", ContentType.Sls },
+ { "application/vnd.epson.salt", ContentType.Slt },
+ { "application/vnd.stepmania.stepchart", ContentType.Sm },
+ { "application/vnd.stardivision.math", ContentType.Smf },
+ { "application/smil+xml", ContentType.Smil },
+ { "video/x-smv", ContentType.Smv },
+ { "application/vnd.stepmania.package", ContentType.Smzip },
+ { "application/x-font-snf", ContentType.Snf },
+ { "text/spdx", ContentType.Spdx },
+ { "application/vnd.yamaha.smaf-phrase", ContentType.Spf },
+ { "application/x-futuresplash", ContentType.Spl },
+ { "text/vnd.in3d.spot", ContentType.Spot },
+ { "application/scvp-vp-response", ContentType.Spp },
+ { "application/scvp-vp-request", ContentType.Spq },
+ { "application/x-sql", ContentType.Sql },
+ { "application/x-wais-source", ContentType.Src },
+ { "application/x-subrip", ContentType.Srt },
+ { "application/sru+xml", ContentType.Sru },
+ { "application/sparql-results+xml", ContentType.Srx },
+ { "application/ssdl+xml", ContentType.Ssdl },
+ { "application/vnd.kodak-descriptor", ContentType.Sse },
+ { "application/vnd.epson.ssf", ContentType.Ssf },
+ { "application/ssml+xml", ContentType.Ssml },
+ { "application/vnd.sailingtracker.track", ContentType.St },
+ { "application/vnd.sun.xml.calc.template", ContentType.Stc },
+ { "application/vnd.sun.xml.draw.template", ContentType.Std },
+ { "application/vnd.wt.stf", ContentType.Stf },
+ { "application/vnd.sun.xml.impress.template", ContentType.Sti },
+ { "application/hyperstudio", ContentType.Stk },
+ { "application/vnd.ms-pki.stl", ContentType.Stl },
+ { "model/step+xml", ContentType.Stpx },
+ { "model/step-xml+zip", ContentType.Stpxz },
+ { "model/step+zip", ContentType.Stpz },
+ { "application/vnd.pg.format", ContentType.Str },
+ { "application/vnd.sun.xml.writer.template", ContentType.Stw },
+ { "text/stylus", ContentType.Stylus },
+ { "image/vnd.dvb.subtitle", ContentType.Sub },
+ { "application/vnd.sus-calendar", ContentType.Sus },
+ { "application/x-sv4cpio", ContentType.Sv4cpio },
+ { "application/x-sv4crc", ContentType.Sv4crc },
+ { "application/vnd.dvb.service", ContentType.Svc },
+ { "application/vnd.svd", ContentType.Svd },
+ { "image/svg+xml", ContentType.Svg },
+ { "application/x-shockwave-flash", ContentType.Swf },
+ { "application/vnd.aristanetworks.swi", ContentType.Swi },
+ { "application/swid+xml", ContentType.Swidtag },
+ { "application/vnd.sun.xml.calc", ContentType.Sxc },
+ { "application/vnd.sun.xml.draw", ContentType.Sxd },
+ { "application/vnd.sun.xml.writer.global", ContentType.Sxg },
+ { "application/vnd.sun.xml.impress", ContentType.Sxi },
+ { "application/vnd.sun.xml.math", ContentType.Sxm },
+ { "application/vnd.sun.xml.writer", ContentType.Sxw },
+ { "application/x-t3vm-image", ContentType.T3 },
+ { "image/t38", ContentType.T38 },
+ { "application/vnd.mynfc", ContentType.Taglet },
+ { "application/vnd.tao.intent-module-archive", ContentType.Tao },
+ { "image/vnd.tencent.tap", ContentType.Tap },
+ { "application/x-tar", ContentType.Tar },
+ { "application/vnd.3gpp2.tcap", ContentType.Tcap },
+ { "application/x-tcl", ContentType.Tcl },
+ { "application/urc-targetdesc+xml", ContentType.Td },
+ { "application/vnd.smart.teacher", ContentType.Teacher },
+ { "application/tei+xml", ContentType.Tei },
+ { "application/x-tex", ContentType.Tex },
+ { "application/x-texinfo", ContentType.Texinfo },
+ { "text/plain", ContentType.Text },
+ { "application/thraud+xml", ContentType.Tfi },
+ { "application/x-tex-tfm", ContentType.Tfm },
+ { "image/tiff-fx", ContentType.Tfx },
+ { "image/x-tga", ContentType.Tga },
+ { "application/vnd.ms-officetheme", ContentType.Thmx },
+ { "image/tiff", ContentType.Tiff },
+ { "application/vnd.tmobile-livetv", ContentType.Tmo },
+ { "application/toml", ContentType.Toml },
+ { "application/x-bittorrent", ContentType.Torrent },
+ { "application/vnd.groove-tool-template", ContentType.Tpl },
+ { "application/vnd.trid.tpt", ContentType.Tpt },
+ { "application/vnd.trueapp", ContentType.Tra },
+ { "application/trig", ContentType.Trig },
+ { "application/x-msterminal", ContentType.Trm },
+ { "video/mp2t", ContentType.Ts },
+ { "application/timestamped-data", ContentType.Tsd },
+ { "text/tab-separated-values", ContentType.Tsv },
+ { "font/collection", ContentType.Ttc },
+ { "font/ttf", ContentType.Ttf },
+ { "text/turtle", ContentType.Ttl },
+ { "application/ttml+xml", ContentType.Ttml },
+ { "application/vnd.simtech-mindmapper", ContentType.Twd },
+ { "application/vnd.genomatix.tuxedo", ContentType.Txd },
+ { "application/vnd.mobius.txf", ContentType.Txf },
+ { "application/x-authorware-bin", ContentType.U32 },
+ { "message/global-delivery-status", ContentType.U8dsn },
+ { "message/global-headers", ContentType.U8hdr },
+ { "message/global-disposition-notification", ContentType.U8mdn },
+ { "message/global", ContentType.U8msg },
+ { "application/ubjson", ContentType.Ubj },
+ { "application/x-debian-package", ContentType.Udeb },
+ { "application/vnd.ufdl", ContentType.Ufdl },
+ { "application/x-glulx", ContentType.Ulx },
+ { "application/vnd.umajin", ContentType.Umj },
+ { "application/vnd.unity", ContentType.Unityweb },
+ { "application/vnd.uoml+xml", ContentType.Uoml },
+ { "text/uri-list", ContentType.Uri },
+ { "model/vnd.usdz+zip", ContentType.Usdz },
+ { "application/x-ustar", ContentType.Ustar },
+ { "application/vnd.uiq.theme", ContentType.Utz },
+ { "text/x-uuencode", ContentType.Uu },
+ { "audio/vnd.dece.audio", ContentType.Uva },
+ { "application/vnd.dece.data", ContentType.Uvd },
+ { "image/vnd.dece.graphic", ContentType.Uvg },
+ { "video/vnd.dece.hd", ContentType.Uvh },
+ { "video/vnd.dece.mobile", ContentType.Uvm },
+ { "video/vnd.dece.pd", ContentType.Uvp },
+ { "video/vnd.dece.sd", ContentType.Uvs },
+ { "application/vnd.dece.ttml+xml", ContentType.Uvt },
+ { "video/vnd.uvvu.mp4", ContentType.Uvu },
+ { "video/vnd.dece.video", ContentType.Uvv },
+ { "application/vnd.dece.zip", ContentType.Uvz },
+ { "application/x-virtualbox-vbox", ContentType.Vbox },
+ { "text/vcard", ContentType.Vcard },
+ { "application/x-cdlink", ContentType.Vcd },
+ { "text/x-vcard", ContentType.Vcf },
+ { "application/vnd.groove-vcard", ContentType.Vcg },
+ { "text/x-vcalendar", ContentType.Vcs },
+ { "application/vnd.vcx", ContentType.Vcx },
+ { "application/x-virtualbox-vdi", ContentType.Vdi },
+ { "model/vnd.sap.vds", ContentType.Vds },
+ { "application/x-virtualbox-vhd", ContentType.Vhd },
+ { "application/vnd.visionary", ContentType.Vis },
+ { "video/vnd.vivo", ContentType.Viv },
+ { "application/x-virtualbox-vmdk", ContentType.Vmdk },
+ { "video/x-ms-vob", ContentType.Vob },
+ { "model/vrml", ContentType.Vrml },
+ { "application/vnd.vsf", ContentType.Vsf },
+ { "application/vnd.visio", ContentType.Vss },
+ { "image/vnd.valve.source.texture", ContentType.Vtf },
+ { "text/vtt", ContentType.Vtt },
+ { "model/vnd.vtu", ContentType.Vtu },
+ { "application/voicexml+xml", ContentType.Vxml },
+ { "application/x-doom", ContentType.Wad },
+ { "application/vnd.sun.wadl+xml", ContentType.Wadl },
+ { "application/wasm", ContentType.Wasm },
+ { "audio/wav", ContentType.Wav },
+ { "audio/x-ms-wax", ContentType.Wax },
+ { "image/vnd.wap.wbmp", ContentType.Wbmp },
+ { "application/vnd.criticaltools.wbs+xml", ContentType.Wbs },
+ { "application/vnd.wap.wbxml", ContentType.Wbxml },
+ { "image/vnd.ms-photo", ContentType.Wdp },
+ { "audio/webm", ContentType.Weba },
+ { "application/x-web-app-manifest+json", ContentType.Webapp },
+ { "video/webm", ContentType.Webm },
+ { "image/webp", ContentType.Webp },
+ { "application/vnd.pmi.widget", ContentType.Wg },
+ { "application/widget", ContentType.Wgt },
+ { "application/vnd.ms-works", ContentType.Wks },
+ { "video/x-ms-wm", ContentType.Wm },
+ { "audio/x-ms-wma", ContentType.Wma },
+ { "application/x-ms-wmd", ContentType.Wmd },
+ { "application/x-msmetafile", ContentType.Wmf },
+ { "text/vnd.wap.wml", ContentType.Wml },
+ { "application/vnd.wap.wmlc", ContentType.Wmlc },
+ { "text/vnd.wap.wmlscript", ContentType.Wmls },
+ { "application/vnd.wap.wmlscriptc", ContentType.Wmlsc },
+ { "video/x-ms-wmv", ContentType.Wmv },
+ { "video/x-ms-wmx", ContentType.Wmx },
+ { "application/x-ms-wmz", ContentType.Wmz },
+ { "font/woff", ContentType.Woff },
+ { "font/woff2", ContentType.Woff2 },
+ { "application/vnd.wordperfect", ContentType.Wpd },
+ { "application/vnd.ms-wpl", ContentType.Wpl },
+ { "application/vnd.wqd", ContentType.Wqd },
+ { "application/x-mswrite", ContentType.Wri },
+ { "message/vnd.wfa.wsc", ContentType.Wsc },
+ { "application/wsdl+xml", ContentType.Wsdl },
+ { "application/wspolicy+xml", ContentType.Wspolicy },
+ { "application/vnd.webturbo", ContentType.Wtb },
+ { "video/x-ms-wvx", ContentType.Wvx },
+ { "model/x3d+xml", ContentType.X3d },
+ { "model/x3d+binary", ContentType.X3db },
+ { "model/x3d+vrml", ContentType.X3dv },
+ { "application/xaml+xml", ContentType.Xaml },
+ { "application/x-silverlight-app", ContentType.Xap },
+ { "application/vnd.xara", ContentType.Xar },
+ { "application/xcap-att+xml", ContentType.Xav },
+ { "application/x-ms-xbap", ContentType.Xbap },
+ { "application/vnd.fujixerox.docuworks.binder", ContentType.Xbd },
+ { "image/x-xbitmap", ContentType.Xbm },
+ { "application/xcap-caps+xml", ContentType.Xca },
+ { "application/calendar+xml", ContentType.Xcs },
+ { "application/xcap-diff+xml", ContentType.Xdf },
+ { "application/vnd.syncml.dm+xml", ContentType.Xdm },
+ { "application/vnd.adobe.xdp+xml", ContentType.Xdp },
+ { "application/dssc+xml", ContentType.Xdssc },
+ { "application/vnd.fujixerox.docuworks", ContentType.Xdw },
+ { "application/xcap-el+xml", ContentType.Xel },
+ { "application/xenc+xml", ContentType.Xenc },
+ { "application/patch-ops-error+xml", ContentType.Xer },
+ { "application/vnd.adobe.xfdf", ContentType.Xfdf },
+ { "application/vnd.xfdl", ContentType.Xfdl },
+ { "application/xhtml+xml", ContentType.Xhtml },
+ { "image/vnd.xiff", ContentType.Xif },
+ { "application/vnd.ms-excel.addin.macroenabled.12", ContentType.Xlam },
+ { "application/x-xliff+xml", ContentType.Xlf },
+ { "application/vnd.ms-excel", ContentType.Xls },
+ { "application/vnd.ms-excel.sheet.binary.macroenabled.12", ContentType.Xlsb },
+ { "application/vnd.ms-excel.sheet.macroenabled.12", ContentType.Xlsm },
+ { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ContentType.Xlsx },
+ { "application/vnd.ms-excel.template.macroenabled.12", ContentType.Xltm },
+ { "application/vnd.openxmlformats-officedocument.spreadsheetml.template", ContentType.Xltx },
+ { "audio/xm", ContentType.Xm },
+ { "application/xml", ContentType.Xml },
+ { "application/xcap-ns+xml", ContentType.Xns },
+ { "application/vnd.olpc-sugar", ContentType.Xo },
+ { "application/xop+xml", ContentType.Xop },
+ { "application/x-xpinstall", ContentType.Xpi },
+ { "application/xproc+xml", ContentType.Xpl },
+ { "image/x-xpixmap", ContentType.Xpm },
+ { "application/vnd.is-xpr", ContentType.Xpr },
+ { "application/vnd.ms-xpsdocument", ContentType.Xps },
+ { "application/vnd.intercon.formnet", ContentType.Xpw },
+ { "application/xslt+xml", ContentType.Xslt },
+ { "application/vnd.syncml+xml", ContentType.Xsm },
+ { "application/vnd.mozilla.xul+xml", ContentType.Xul },
+ { "application/xv+xml", ContentType.Xvml },
+ { "image/x-xwindowdump", ContentType.Xwd },
+ { "chemical/x-xyz", ContentType.Xyz },
+ { "application/x-xz", ContentType.Xz },
+ { "text/yaml", ContentType.Yaml },
+ { "application/yang", ContentType.Yang },
+ { "application/yin+xml", ContentType.Yin },
+ { "text/x-suse-ymp", ContentType.Ymp },
+ { "application/x-zmachine", ContentType.Z1 },
+ { "application/vnd.zzazz.deck+xml", ContentType.Zaz },
+ { "application/zip", ContentType.Zip },
+ { "application/vnd.zul", ContentType.Zir },
+ { "application/vnd.handheld-entertainment+xml", ContentType.Zmm },
+ };
+ }
+}
diff --git a/Net.Http/src/Helpers/TransportReader.cs b/Net.Http/src/Helpers/TransportReader.cs
new file mode 100644
index 0000000..a37bfe9
--- /dev/null
+++ b/Net.Http/src/Helpers/TransportReader.cs
@@ -0,0 +1,114 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TransportReader.cs
+*
+* TransportReader.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+
+
+namespace VNLib.Net.Http.Core
+{
+
+ /// <summary>
+ /// Structure implementation of <see cref="IVnTextReader"/>
+ /// </summary>
+ internal struct TransportReader : IVnTextReader
+ {
+ ///<inheritdoc/>
+ public readonly Encoding Encoding => _encoding;
+ ///<inheritdoc/>
+ public readonly ReadOnlyMemory<byte> LineTermination => _lineTermination;
+ ///<inheritdoc/>
+ public readonly Stream BaseStream => _transport;
+
+
+ private readonly SharedHeaderReaderBuffer BinBuffer;
+ private readonly Encoding _encoding;
+ private readonly Stream _transport;
+ private readonly ReadOnlyMemory<byte> _lineTermination;
+
+ private int BufWindowStart;
+ private int BufWindowEnd;
+
+ /// <summary>
+ /// Initializes a new <see cref="TransportReader"/> for reading text lines from the transport stream
+ /// </summary>
+ /// <param name="transport">The transport stream to read data from</param>
+ /// <param name="buffer">The shared binary buffer</param>
+ /// <param name="encoding">The encoding to use when reading bianry</param>
+ /// <param name="lineTermination">The line delimiter to search for</param>
+ public TransportReader(Stream transport, SharedHeaderReaderBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
+ {
+ BufWindowEnd = 0;
+ BufWindowStart = 0;
+ _encoding = encoding;
+ _transport = transport;
+ _lineTermination = lineTermination;
+ BinBuffer = buffer;
+ }
+
+ ///<inheritdoc/>
+ public readonly int Available => BufWindowEnd - BufWindowStart;
+
+ ///<inheritdoc/>
+ public readonly Span<byte> BufferedDataWindow => BinBuffer.BinBuffer[BufWindowStart..BufWindowEnd];
+
+
+ ///<inheritdoc/>
+ public void Advance(int count) => BufWindowStart += count;
+ ///<inheritdoc/>
+ public void FillBuffer()
+ {
+ //Get a buffer from the end of the current window to the end of the buffer
+ Span<byte> bufferWindow = BinBuffer.BinBuffer[BufWindowEnd..];
+ //Read from stream
+ int read = _transport.Read(bufferWindow);
+ //Update the end of the buffer window to the end of the read data
+ BufWindowEnd += read;
+ }
+ ///<inheritdoc/>
+ public ERRNO CompactBufferWindow()
+ {
+ //No data to compact if window is not shifted away from start
+ if (BufWindowStart > 0)
+ {
+ //Get span over engire buffer
+ Span<byte> buffer = BinBuffer.BinBuffer;
+ //Get data within window
+ Span<byte> usedData = buffer[BufWindowStart..BufWindowEnd];
+ //Copy remaining to the begining of the buffer
+ usedData.CopyTo(buffer);
+ //Buffer window start is 0
+ BufWindowStart = 0;
+ //Buffer window end is now the remaining size
+ BufWindowEnd = usedData.Length;
+ }
+ //Return the number of bytes of available space
+ return BinBuffer.BinLength - BufWindowEnd;
+ }
+ }
+}
diff --git a/Net.Http/src/Helpers/VnWebHeaderCollection.cs b/Net.Http/src/Helpers/VnWebHeaderCollection.cs
new file mode 100644
index 0000000..f67e7db
--- /dev/null
+++ b/Net.Http/src/Helpers/VnWebHeaderCollection.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: VnWebHeaderCollection.cs
+*
+* VnWebHeaderCollection.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Collections.Generic;
+
+
+namespace VNLib.Net.Http
+{
+ ///<inheritdoc/>
+ public sealed class VnWebHeaderCollection : WebHeaderCollection, IEnumerable<KeyValuePair<string?, string?>>
+ {
+ IEnumerator<KeyValuePair<string?, string?>> IEnumerable<KeyValuePair<string?, string?>>.GetEnumerator()
+ {
+ for (int i = 0; i < Keys.Count; i++)
+ {
+ yield return new(Keys[i], Get(i));
+ }
+ }
+ }
+}
diff --git a/Net.Http/src/Helpers/WebHeaderExtensions.cs b/Net.Http/src/Helpers/WebHeaderExtensions.cs
new file mode 100644
index 0000000..e51bdd5
--- /dev/null
+++ b/Net.Http/src/Helpers/WebHeaderExtensions.cs
@@ -0,0 +1,60 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: WebHeaderExtensions.cs
+*
+* WebHeaderExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Net;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Extends the <see cref="WebHeaderCollection"/> to provide some check methods
+ /// </summary>
+ public static class WebHeaderExtensions
+ {
+ /// <summary>
+ /// Determines if the specified request header has been set in the current header collection
+ /// </summary>
+ /// <param name="headers"></param>
+ /// <param name="header">Header value to check</param>
+ /// <returns>true if the header was set, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HeaderSet(this WebHeaderCollection headers, HttpRequestHeader header) => !string.IsNullOrWhiteSpace(headers[header]);
+ /// <summary>
+ /// Determines if the specified response header has been set in the current header collection
+ /// </summary>
+ /// <param name="headers"></param>
+ /// <param name="header">Header value to check</param>
+ /// <returns>true if the header was set, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HeaderSet(this WebHeaderCollection headers, HttpResponseHeader header) => !string.IsNullOrWhiteSpace(headers[header]);
+ /// <summary>
+ /// Determines if the specified header has been set in the current header collection
+ /// </summary>
+ /// <param name="headers"></param>
+ /// <param name="header">Header value to check</param>
+ /// <returns>true if the header was set, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HeaderSet(this WebHeaderCollection headers, string header) => !string.IsNullOrWhiteSpace(headers[header]);
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/HttpConfig.cs b/Net.Http/src/HttpConfig.cs
new file mode 100644
index 0000000..8e73176
--- /dev/null
+++ b/Net.Http/src/HttpConfig.cs
@@ -0,0 +1,154 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpConfig.cs
+*
+* HttpConfig.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Text;
+using System.IO.Compression;
+
+using VNLib.Utils.Logging;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents configration variables used to create the instance and manage http connections
+ /// </summary>
+ public readonly struct HttpConfig
+ {
+ public HttpConfig(ILogProvider log)
+ {
+ ConnectionKeepAlive = TimeSpan.FromSeconds(100);
+ ServerLog = log;
+ }
+
+ /// <summary>
+ /// A log provider that all server related log entiries will be written to
+ /// </summary>
+ public readonly ILogProvider ServerLog { get; }
+ /// <summary>
+ /// The absolute request entity body size limit in bytes
+ /// </summary>
+ public readonly int MaxUploadSize { get; init; } = 5 * 1000 * 1024;
+ /// <summary>
+ /// The maximum size in bytes allowed for an MIME form-data content type upload
+ /// </summary>
+ /// <remarks>Set to 0 to disabled mulit-part/form-data uploads</remarks>
+ public readonly int MaxFormDataUploadSize { get; init; } = 40 * 1024;
+ /// <summary>
+ /// The maximum buffer size to use when parsing Multi-part/Form-data file uploads
+ /// </summary>
+ /// <remarks>
+ /// This value is used to create the buffer used to read data from the input stream
+ /// into memory for parsing. Form-data uploads must be parsed in memory because
+ /// the data is not delimited by a content length.
+ /// </remarks>
+ public readonly int FormDataBufferSize { get; init; } = 8192;
+ /// <summary>
+ /// The maximum response entity size in bytes for which the library will allow compresssing response data
+ /// </summary>
+ /// <remarks>Set this value to 0 to disable response compression</remarks>
+ public readonly int CompressionLimit { get; init; } = 1000 * 1024;
+ /// <summary>
+ /// The minimum size (in bytes) of respones data that will be compressed
+ /// </summary>
+ public readonly int CompressionMinimum { get; init; } = 4096;
+ /// <summary>
+ /// The maximum amount of time to listen for data from a connected, but inactive transport connection
+ /// before closing them
+ /// </summary>
+ public readonly TimeSpan ConnectionKeepAlive { get; init; }
+ /// <summary>
+ /// The encoding to use when sending and receiving HTTP data
+ /// </summary>
+ public readonly Encoding HttpEncoding { get; init; } = Encoding.UTF8;
+ /// <summary>
+ /// Sets the compression level for response entity streams of all supported types when
+ /// compression is used.
+ /// </summary>
+ public readonly CompressionLevel CompressionLevel { get; init; } = CompressionLevel.Optimal;
+ /// <summary>
+ /// Sets the default Http version for responses when the client version cannot be parsed from the request
+ /// </summary>
+ public readonly HttpVersion DefaultHttpVersion { get; init; } = HttpVersion.Http11;
+ /// <summary>
+ /// The buffer size used to read HTTP headers from the transport.
+ /// </summary>
+ /// <remarks>
+ /// Setting this value too low will result in header parsing failures
+ /// and 400 Bad Request responses. Setting it too high can result in
+ /// resource abuse or high memory usage. 8k is usually a good value.
+ /// </remarks>
+ public readonly int HeaderBufferSize { get; init; } = 8192;
+ /// <summary>
+ /// The amount of time (in milliseconds) to wait for data on a connection that is in a receive
+ /// state, aka active receive.
+ /// </summary>
+ public readonly int ActiveConnectionRecvTimeout { get; init; } = 5000;
+ /// <summary>
+ /// The amount of time (in milliseconds) to wait for data to be send to the client before
+ /// the connection is closed
+ /// </summary>
+ public readonly int SendTimeout { get; init; } = 5000;
+ /// <summary>
+ /// The maximum number of request headers allowed per request
+ /// </summary>
+ public readonly int MaxRequestHeaderCount { get; init; } = 100;
+ /// <summary>
+ /// The maximum number of open transport connections, before 503 errors
+ /// will be returned and new connections closed.
+ /// </summary>
+ /// <remarks>Set to 0 to disable request processing. Causes perminant 503 results</remarks>
+ public readonly int MaxOpenConnections { get; init; } = int.MaxValue;
+ /// <summary>
+ /// The size (in bytes) of the http response header accumulator buffer.
+ /// </summary>
+ /// <remarks>
+ /// Http responses use an internal accumulator to buffer all response headers
+ /// before writing them to the transport in on write operation. If this value
+ /// is too low, the response will fail to write. If it is too high, it
+ /// may cause resource exhaustion or high memory usage.
+ /// </remarks>
+ public readonly int ResponseHeaderBufferSize { get; init; } = 16 * 1024;
+ /// <summary>
+ /// The size (in bytes) of the buffer to use to discard unread request entity bodies
+ /// </summary>
+ public readonly int DiscardBufferSize { get; init; } = 64 * 1024;
+ /// <summary>
+ /// The size of the buffer to use when writing response data to the transport
+ /// </summary>
+ /// <remarks>
+ /// This value is the size of the buffer used to copy data from the response
+ /// entity stream, to the transport stream.
+ /// </remarks>
+ public readonly int ResponseBufferSize { get; init; } = 32 * 1024;
+ /// <summary>
+ /// The size of the buffer used to accumulate chunked response data before writing to the transport
+ /// </summary>
+ public readonly int ChunkedResponseAccumulatorSize { get; init; } = 64 * 1024;
+ /// <summary>
+ /// An <see cref="ILogProvider"/> for writing verbose request logs. Set to <c>null</c>
+ /// to disable verbose request logging
+ /// </summary>
+ public readonly ILogProvider? RequestDebugLog { get; init; } = null;
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/IAlternateProtocol.cs b/Net.Http/src/IAlternateProtocol.cs
new file mode 100644
index 0000000..dc7072b
--- /dev/null
+++ b/Net.Http/src/IAlternateProtocol.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IAlternateProtocol.cs
+*
+* IAlternateProtocol.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Allows implementation for a protocol swtich from HTTP to another protocol
+ /// </summary>
+ public interface IAlternateProtocol
+ {
+ /// <summary>
+ /// Initializes and executes the protocol-switch and the protocol handler
+ /// that is stored
+ /// </summary>
+ /// <param name="transport">The prepared transport stream for the new protocol</param>
+ /// <param name="handlerToken">A cancelation token that the caller may pass for operation cancelation and cleanup</param>
+ /// <returns>A task that will be awaited by the server, that when complete, will cleanup resources held by the connection</returns>
+ Task RunAsync(Stream transport, CancellationToken handlerToken);
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/IConnectionInfo.cs b/Net.Http/src/IConnectionInfo.cs
new file mode 100644
index 0000000..17ee16f
--- /dev/null
+++ b/Net.Http/src/IConnectionInfo.cs
@@ -0,0 +1,150 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IConnectionInfo.cs
+*
+* IConnectionInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Text;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents a client's connection info as interpreted by the current server
+ /// </summary>
+ /// <remarks>Methods and properties are undefined when <see cref="IWebRoot.ClientConnectedAsync(IHttpEvent)"/> returns</remarks>
+ public interface IConnectionInfo
+ {
+ /// <summary>
+ /// Full request uri of current connection
+ /// </summary>
+ Uri RequestUri { get; }
+ /// <summary>
+ /// Current request path. Shortcut to <seealso cref="RequestUri"/> <see cref="Uri.LocalPath"/>
+ /// </summary>
+ string Path => RequestUri.LocalPath;
+ /// <summary>
+ /// Current connection's user-agent header, (may be null if no user-agent header found)
+ /// </summary>
+ string? UserAgent { get; }
+ /// <summary>
+ /// Current connection's headers
+ /// </summary>
+ IHeaderCollection Headers { get; }
+ /// <summary>
+ /// A value that indicates if the connection's origin header was set and it's
+ /// authority segment does not match the <see cref="RequestUri"/> authority
+ /// segment.
+ /// </summary>
+ bool CrossOrigin { get; }
+ /// <summary>
+ /// Is the current connecion a websocket request
+ /// </summary>
+ bool IsWebSocketRequest { get; }
+ /// <summary>
+ /// Request specified content-type
+ /// </summary>
+ ContentType ContentType { get; }
+ /// <summary>
+ /// Current request's method
+ /// </summary>
+ HttpMethod Method { get; }
+ /// <summary>
+ /// The current connection's HTTP protocol version
+ /// </summary>
+ HttpVersion ProtocolVersion { get; }
+ /// <summary>
+ /// Is the connection using transport security?
+ /// </summary>
+ bool IsSecure { get; }
+ /// <summary>
+ /// The negotiated transport protocol for the current connection
+ /// </summary>
+ SslProtocols SecurityProtocol { get; }
+ /// <summary>
+ /// Origin header of current connection if specified, null otherwise
+ /// </summary>
+ Uri? Origin { get; }
+ /// <summary>
+ /// Referer header of current connection if specified, null otherwise
+ /// </summary>
+ Uri? Referer { get; }
+ /// <summary>
+ /// The parsed range header, or -1,-1 if the range header was not set
+ /// </summary>
+ Tuple<long, long>? Range { get; }
+ /// <summary>
+ /// The server endpoint that accepted the connection
+ /// </summary>
+ IPEndPoint LocalEndpoint { get; }
+ /// <summary>
+ /// The raw <see cref="IPEndPoint"/> of the downstream connection.
+ /// </summary>
+ IPEndPoint RemoteEndpoint { get; }
+ /// <summary>
+ /// The encoding type used to decode and encode character data to and from the current client
+ /// </summary>
+ Encoding Encoding { get; }
+ /// <summary>
+ /// A <see cref="IReadOnlyDictionary{TKey, TValue}"/> of client request cookies
+ /// </summary>
+ IReadOnlyDictionary<string, string> RequestCookies { get; }
+ /// <summary>
+ /// Gets an <see cref="IEnumerator{T}"/> for the parsed accept header values
+ /// </summary>
+ IEnumerable<string> Accept { get; }
+ /// <summary>
+ /// Gets the underlying transport security information for the current connection
+ /// </summary>
+ TransportSecurityInfo? TransportSecurity { get; }
+
+ /// <summary>
+ /// Determines if the client accepts the response content type
+ /// </summary>
+ /// <param name="type">The desired content type</param>
+ /// <returns>True if the client accepts the content type, false otherwise</returns>
+ bool Accepts(ContentType type);
+
+ /// <summary>
+ /// Determines if the client accepts the response content type
+ /// </summary>
+ /// <param name="contentType">The desired content type</param>
+ /// <returns>True if the client accepts the content type, false otherwise</returns>
+ bool Accepts(string contentType);
+
+ /// <summary>
+ /// Adds a new cookie to the response. If a cookie with the same name and value
+ /// has been set, the old cookie is replaced with the new one.
+ /// </summary>
+ /// <param name="name">Cookie name/id</param>
+ /// <param name="value">Value to be stored in cookie</param>
+ /// <param name="domain">Domain for cookie to operate</param>
+ /// <param name="path">Path to store cookie</param>
+ /// <param name="Expires">Timespan representing how long the cookie should exist</param>
+ /// <param name="sameSite">Samesite attribute, Default = Lax</param>
+ /// <param name="httpOnly">Specify the HttpOnly flag</param>
+ /// <param name="secure">Specify the Secure flag</param>
+ void SetCookie(string name, string value, string? domain, string? path, TimeSpan Expires, CookieSameSite sameSite, bool httpOnly, bool secure);
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/IHeaderCollection.cs b/Net.Http/src/IHeaderCollection.cs
new file mode 100644
index 0000000..f7f147a
--- /dev/null
+++ b/Net.Http/src/IHeaderCollection.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHeaderCollection.cs
+*
+* IHeaderCollection.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Net;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// The container for request and response headers
+ /// </summary>
+ public interface IHeaderCollection
+ {
+ /// <summary>
+ /// Allows for enumeratring all requesest headers
+ /// </summary>
+ IEnumerable<KeyValuePair<string, string>> RequestHeaders { get; }
+ /// <summary>
+ /// Allows for enumeratring all response headers
+ /// </summary>
+ IEnumerable<KeyValuePair<string, string>> ResponseHeaders { get; }
+ /// <summary>
+ /// Gets request header, or sets a response header
+ /// </summary>
+ /// <param name="index"></param>
+ /// <returns>Request header with key</returns>
+ string? this[string index] { get; set; }
+ /// <summary>
+ /// Sets a response header only with a response header index
+ /// </summary>
+ /// <param name="index">Response header</param>
+ string this[HttpResponseHeader index] { set; }
+ /// <summary>
+ /// Gets a request header
+ /// </summary>
+ /// <param name="index">The request header enum </param>
+ string? this[HttpRequestHeader index] { get; }
+ /// <summary>
+ /// Determines if the given header is set in current response headers
+ /// </summary>
+ /// <param name="header">Header value to check response headers for</param>
+ /// <returns>true if header exists in current response headers, false otherwise</returns>
+ bool HeaderSet(HttpResponseHeader header);
+ /// <summary>
+ /// Determines if the given request header is set in current request headers
+ /// </summary>
+ /// <param name="header">Header value to check request headers for</param>
+ /// <returns>true if header exists in current request headers, false otherwise</returns>
+ bool HeaderSet(HttpRequestHeader header);
+
+ /// <summary>
+ /// Overwrites (sets) the given response header to the exact value specified
+ /// </summary>
+ /// <param name="header">The enumrated header id</param>
+ /// <param name="value">The value to specify</param>
+ void Append(HttpResponseHeader header, string? value);
+ /// <summary>
+ /// Overwrites (sets) the given response header to the exact value specified
+ /// </summary>
+ /// <param name="header">The header name</param>
+ /// <param name="value">The value to specify</param>
+ void Append(string header, string? value);
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/IMemoryResponseEntity.cs b/Net.Http/src/IMemoryResponseEntity.cs
new file mode 100644
index 0000000..aa77f58
--- /dev/null
+++ b/Net.Http/src/IMemoryResponseEntity.cs
@@ -0,0 +1,69 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IMemoryResponseEntity.cs
+*
+* IMemoryResponseEntity.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// <para>
+ /// A forward only memory backed response entity body reader. This interface exists
+ /// to provide a memory-backed response body that will be written "directly" to the
+ /// response stream. This avoids a buffer allocation and a copy.
+ /// </para>
+ /// <para>
+ /// The entity is only read foward, one time, so it is not seekable.
+ /// </para>
+ /// <para>
+ /// The <see cref="Close"/> method is always called by internal lifecycle hooks
+ /// when the entity is no longer needed. <see cref="Close"/> should avoid raising
+ /// excptions.
+ /// </para>
+ /// </summary>
+ public interface IMemoryResponseReader
+ {
+ /// <summary>
+ /// Gets a readonly buffer containing the remaining
+ /// data to be written
+ /// </summary>
+ /// <returns>A memory segment to send to the client</returns>
+ ReadOnlyMemory<byte> GetMemory();
+
+ /// <summary>
+ /// Advances the buffer by the number of bytes written
+ /// </summary>
+ /// <param name="written">The number of bytes written</param>
+ void Advance(int written);
+
+ /// <summary>
+ /// The number of bytes remaining to send
+ /// </summary>
+ int Remaining { get; }
+
+ /// <summary>
+ /// Raised when reading has completed
+ /// </summary>
+ void Close();
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/ITransportContext.cs b/Net.Http/src/ITransportContext.cs
new file mode 100644
index 0000000..bd6ce05
--- /dev/null
+++ b/Net.Http/src/ITransportContext.cs
@@ -0,0 +1,71 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ITransportContext.cs
+*
+* ITransportContext.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using System.Security.Authentication;
+
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents an active connection for application data processing
+ /// </summary>
+ public interface ITransportContext
+ {
+ /// <summary>
+ /// The transport network stream for application data marshaling
+ /// </summary>
+ Stream ConnectionStream { get; }
+ /// <summary>
+ /// The transport security layer security protocol
+ /// </summary>
+ SslProtocols SslVersion { get; }
+ /// <summary>
+ /// A copy of the local endpoint of the listening socket
+ /// </summary>
+ IPEndPoint LocalEndPoint { get; }
+ /// <summary>
+ /// The <see cref="IPEndPoint"/> representing the client's connection information
+ /// </summary>
+ IPEndPoint RemoteEndpoint { get; }
+
+ /// <summary>
+ /// Closes the connection when its no longer in use and cleans up held resources.
+ /// </summary>
+ /// <returns></returns>
+ /// <remarks>
+ /// This method will always be called by the server when a connection is complete
+ /// regardless of the state of the trasnport
+ /// </remarks>
+ ValueTask CloseConnectionAsync();
+
+ /// <summary>
+ /// Attemts to get the transport security details for the connection
+ /// </summary>
+ /// <returns>A the <see cref="TransportSecurityInfo"/> structure if applicable, null otherwise</returns>
+ TransportSecurityInfo? GetSecurityInfo();
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/ITransportProvider.cs b/Net.Http/src/ITransportProvider.cs
new file mode 100644
index 0000000..13aae57
--- /dev/null
+++ b/Net.Http/src/ITransportProvider.cs
@@ -0,0 +1,51 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ITransportProvider.cs
+*
+* ITransportProvider.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Listens for network connections and captures the information
+ /// required for application processing
+ /// </summary>
+ public interface ITransportProvider
+ {
+ /// <summary>
+ /// Begins listening for connections (binds a socket if necessary) and is
+ /// called before the server begins listening for connections.
+ /// </summary>
+ /// <param name="stopToken">A token that is cancelled when the server is closed</param>
+ void Start(CancellationToken stopToken);
+
+ /// <summary>
+ /// Waits for a new connection to be established and returns its context. This method
+ /// should only return an established connection (ie: connected socket).
+ /// </summary>
+ /// <param name="cancellation">A token to cancel the wait operation</param>
+ /// <returns>A <see cref="ValueTask"/> that returns an established connection</returns>
+ ValueTask<ITransportContext> AcceptAsync(CancellationToken cancellation);
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/IWebRoot.cs b/Net.Http/src/IWebRoot.cs
new file mode 100644
index 0000000..9b1a9c8
--- /dev/null
+++ b/Net.Http/src/IWebRoot.cs
@@ -0,0 +1,58 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IWebRoot.cs
+*
+* IWebRoot.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents a root identifying the main endpoints of the server, and the primary processing actions
+ /// for requests to this endpoint
+ /// </summary>
+ public interface IWebRoot
+ {
+ /// <summary>
+ /// The hostname the server will listen for, and the hostname that will identify this root when a connection requests it
+ /// </summary>
+ string Hostname { get; }
+ /// <summary>
+ /// <para>
+ /// The main event handler for user code to process a request
+ /// </para>
+ /// <para>
+ /// NOTE: This function must be thread-safe!
+ /// </para>
+ /// </summary>
+ /// <param name="httpEvent">An active, unprocessed event capturing the request infomration into a standard format</param>
+ /// <returns>A <see cref="ValueTask"/> that the processor will await until the entity has been processed</returns>
+ ValueTask ClientConnectedAsync(IHttpEvent httpEvent);
+ /// <summary>
+ /// "Low-Level" 301 redirects
+ /// </summary>
+ IReadOnlyDictionary<string, Redirect> Redirects { get; }
+ }
+} \ No newline at end of file
diff --git a/Net.Http/src/TransportSecurityInfo.cs b/Net.Http/src/TransportSecurityInfo.cs
new file mode 100644
index 0000000..7c7a79c
--- /dev/null
+++ b/Net.Http/src/TransportSecurityInfo.cs
@@ -0,0 +1,121 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TransportSecurityInfo.cs
+*
+* TransportSecurityInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+
+
+namespace VNLib.Net.Http
+{
+
+ /// <summary>
+ /// Gets the transport TLS security information for the current connection
+ /// </summary>
+ public readonly struct TransportSecurityInfo
+ {
+ /// <summary>
+ /// Gets a Boolean value that indicates whether the certificate revocation list is checked during the certificate validation process.
+ /// </summary>
+ /// <returns>true if the certificate revocation list is checked during validation; otherwise, false.</returns>
+ public readonly bool CheckCertRevocationStatus { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the bulk encryption algorithm used by the connection.
+ /// </summary>
+ public readonly CipherAlgorithmType CipherAlgorithm { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the strength of the cipher algorithm used by the connection.
+ /// </summary>
+ public readonly int CipherStrength { get; init; }
+
+ /// <summary>
+ /// Gets the algorithm used for generating message authentication codes (MACs).
+ /// </summary>
+ public readonly HashAlgorithmType HashAlgorithm { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the strength of the hash algorithm used by this instance.
+ /// </summary>
+ public readonly int HashStrength { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether authentication was successful.
+ /// </summary>
+ public readonly bool IsAuthenticated { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether this connection uses data encryption.
+ /// </summary>
+ public readonly bool IsEncrypted { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether both server and client have been authenticated.
+ /// </summary>
+ public readonly bool IsMutuallyAuthenticated { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether the data sent using this connection is signed.
+ /// </summary>
+ public readonly bool IsSigned { get; init; }
+
+ /// <summary>
+ /// Gets the key exchange algorithm used by this connection
+ /// </summary>
+ public readonly ExchangeAlgorithmType KeyExchangeAlgorithm { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the strength of the key exchange algorithm used by the transport connection
+ /// </summary>
+ public readonly int KeyExchangeStrength { get; init; }
+
+ /// <summary>
+ /// Gets the certificate used to authenticate the local endpoint.
+ /// </summary>
+ public readonly X509Certificate? LocalCertificate { get; init; }
+
+ /// <summary>
+ /// The negotiated application protocol in TLS handshake.
+ /// </summary>
+ public readonly SslApplicationProtocol NegotiatedApplicationProtocol { get; init; }
+
+ /// <summary>
+ /// Gets the cipher suite which was negotiated for this connection.
+ /// </summary>
+ public readonly TlsCipherSuite NegotiatedCipherSuite { get; init; }
+
+ /// <summary>
+ /// Gets the certificate used to authenticate the remote endpoint.
+ /// </summary>
+ public readonly X509Certificate? RemoteCertificate { get; init; }
+
+ /// <summary>
+ /// Gets the TransportContext used for authentication using extended protection.
+ /// </summary>
+ public readonly TransportContext TransportContext { get; init; }
+ }
+}
diff --git a/Net.Http/src/VNLib.Net.Http.csproj b/Net.Http/src/VNLib.Net.Http.csproj
new file mode 100644
index 0000000..30e698c
--- /dev/null
+++ b/Net.Http/src/VNLib.Net.Http.csproj
@@ -0,0 +1,57 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <RootNamespace>VNLib.Net.Http</RootNamespace>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>$(Authors)</Company>
+ <Product>VNLib HTTP Library</Product>
+ <Description>Provides a high performance HTTP 0.9-1.1 application processing layer for handling transport *agnostic connections and asynchronous event support for applications serving HTTP
+requests such as web content. This library has a large focus on low/no GC allocations using unmanaged memory support provided by the VNLib.Utils library. No external dependencies
+outside of the VNLib ecosystem are required. The VNLib.Plugins and VNLib.Plugins.Essentials libraries are highly recommended for serving web content.</Description>
+ <Copyright>Copyright © 2022 Vaughn Nugent</Copyright>
+ <PackageId>VNLib.Net.Http</PackageId>
+ <Version>1.0.1.5</Version>
+ <NeutralLanguage>en-US</NeutralLanguage>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
+ <AssemblyName>VNLib.Net.Http</AssemblyName>
+ <Nullable>enable</Nullable>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <SignAssembly>True</SignAssembly>
+ <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+
+ <!-- Resolve nuget dll files and store them in the output dir -->
+ <PropertyGroup>
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+ <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\Utils\src\VNLib.Utils.csproj" />
+ </ItemGroup>
+
+</Project>