aboutsummaryrefslogtreecommitdiff
path: root/Net.Http/src/Helpers/HttpHelpers.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Net.Http/src/Helpers/HttpHelpers.cs')
-rw-r--r--Net.Http/src/Helpers/HttpHelpers.cs445
1 files changed, 0 insertions, 445 deletions
diff --git a/Net.Http/src/Helpers/HttpHelpers.cs b/Net.Http/src/Helpers/HttpHelpers.cs
deleted file mode 100644
index 9cceff1..0000000
--- a/Net.Http/src/Helpers/HttpHelpers.cs
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
-* 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