diff --git a/src/Entity/HttpHeaderCollection.cs b/src/Entity/HttpHeaderCollection.cs index 30f3c80..dbb6367 100644 --- a/src/Entity/HttpHeaderCollection.cs +++ b/src/Entity/HttpHeaderCollection.cs @@ -13,7 +13,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.PortableExecutable; using System.Text; -using Header = Sisk.Core.Internal.HttpKnownHeaderNames; +using Header = Sisk.Core.Http.HttpKnownHeaderNames; namespace Sisk.Core.Entity; diff --git a/src/Entity/MultipartFormReader.cs b/src/Entity/MultipartFormReader.cs index 3352635..056d00e 100644 --- a/src/Entity/MultipartFormReader.cs +++ b/src/Entity/MultipartFormReader.cs @@ -7,6 +7,7 @@ // File name: MultipartFormReader.cs // Repository: https://github.com/sisk-http/core +using Sisk.Core.Http; using Sisk.Core.Internal; using System; using System.Collections.Generic; diff --git a/src/Entity/MultipartObject.cs b/src/Entity/MultipartObject.cs index b3c422b..e80357b 100644 --- a/src/Entity/MultipartObject.cs +++ b/src/Entity/MultipartObject.cs @@ -8,7 +8,6 @@ // Repository: https://github.com/sisk-http/core using Sisk.Core.Http; -using Sisk.Core.Internal; using System.Collections.Specialized; using System.Text; diff --git a/src/Http/CookieHelpers.cs b/src/Http/CookieHelpers.cs index feb05e8..068b8cc 100644 --- a/src/Http/CookieHelpers.cs +++ b/src/Http/CookieHelpers.cs @@ -7,7 +7,6 @@ // File name: CookieHelpers.cs // Repository: https://github.com/sisk-http/core -using Sisk.Core.Internal; using System.Web; namespace Sisk.Core.Http; diff --git a/src/Http/Handlers/AsyncHttpServerHandler.cs b/src/Http/Handlers/AsyncHttpServerHandler.cs new file mode 100644 index 0000000..53770d3 --- /dev/null +++ b/src/Http/Handlers/AsyncHttpServerHandler.cs @@ -0,0 +1,134 @@ +// The Sisk Framework source code +// Copyright (c) 2023 PROJECT PRINCIPIUM +// +// The code below is licensed under the MIT license as +// of the date of its publication, available at +// +// File name: AsyncHttpServerHandler.cs +// Repository: https://github.com/sisk-http/core + +using Sisk.Core.Entity; +using Sisk.Core.Routing; + +namespace Sisk.Core.Http.Handlers; + +/// +/// Represents an asynchronous event handler for the , router, and related events. +/// +public abstract class AsyncHttpServerHandler : HttpServerHandler +{ + /// + /// Method that is called immediately before starting the . + /// + /// The HTTP server entity which is starting. + protected virtual Task OnServerStartingAsync(HttpServer server) => Task.CompletedTask; + + /// + /// Method that is called immediately after starting the , when it's + /// ready and listening. + /// + /// The HTTP server entity which is ready. + protected virtual Task OnServerStartedAsync(HttpServer server) => Task.CompletedTask; + + /// + /// Method that is called before the stop, when it is + /// stopping from listening requests. + /// + /// The HTTP server entity which is stopping. + protected virtual Task OnServerStoppingAsync(HttpServer server) => Task.CompletedTask; + + /// + /// Method that is called after the is stopped, meaning + /// it has stopped from listening to requests. + /// + /// The HTTP server entity which has stopped. + protected virtual Task OnServerStoppedAsync(HttpServer server) => Task.CompletedTask; + + /// + /// Method that is called when an is binded to the HTTP server. + /// + /// The router entity which is binded. + protected virtual Task OnSetupRouterAsync(Router router) => Task.CompletedTask; + + /// + /// Method that is called when an HTTP context is created within an + /// object. + /// + /// The creating context bag. + protected virtual Task OnContextBagCreatedAsync(TypedValueDictionary contextBag) => Task.CompletedTask; + + /// + /// Method that is called when an is received in the + /// HTTP server. + /// + /// The connecting HTTP request entity. + protected virtual Task OnHttpRequestOpenAsync(HttpRequest request) => Task.CompletedTask; + + /// + /// Method that is called when an is closed in the + /// HTTP server. + /// + /// The result of the execution of the request. + protected virtual Task OnHttpRequestCloseAsync(HttpServerExecutionResult result) => Task.CompletedTask; + + /// + /// Method that is called when an exception is caught in the HTTP server. This method is called + /// regardless of whether is enabled or not. + /// + /// The exception object. + protected virtual Task OnExceptionAsync(Exception exception) => Task.CompletedTask; + + /// + protected sealed override void OnContextBagCreated(TypedValueDictionary contextBag) + { + OnContextBagCreatedAsync(contextBag).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnException(Exception exception) + { + OnExceptionAsync(exception).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnHttpRequestClose(HttpServerExecutionResult result) + { + OnHttpRequestCloseAsync(result).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnHttpRequestOpen(HttpRequest request) + { + OnHttpRequestOpenAsync(request).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnServerStarted(HttpServer server) + { + OnServerStartedAsync(server).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnServerStarting(HttpServer server) + { + OnServerStartingAsync(server).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnServerStopped(HttpServer server) + { + OnServerStoppedAsync(server).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnServerStopping(HttpServer server) + { + OnServerStoppingAsync(server).GetAwaiter().GetResult(); + } + + /// + protected sealed override void OnSetupRouter(Router router) + { + OnSetupRouterAsync(router).GetAwaiter().GetResult(); + } +} diff --git a/src/Http/Handlers/HttpServerHandler.cs b/src/Http/Handlers/HttpServerHandler.cs index 9116575..0a40fd3 100644 --- a/src/Http/Handlers/HttpServerHandler.cs +++ b/src/Http/Handlers/HttpServerHandler.cs @@ -7,6 +7,7 @@ // File name: HttpServerHandler.cs // Repository: https://github.com/sisk-http/core +using Sisk.Core.Entity; using Sisk.Core.Routing; namespace Sisk.Core.Http.Handlers; @@ -52,16 +53,15 @@ protected virtual void OnServerStopped(HttpServer server) { } /// /// The router entity which is binded. protected virtual void OnSetupRouter(Router router) { } - internal void InvokeOnSetupRouter(Router router) => OnSetupRouter(router); /// - /// Method that is called when an is created within an + /// Method that is called when an HTTP context is created within an /// object. /// /// The creating context bag. - protected virtual void OnContextBagCreated(HttpContextBagRepository contextBag) { } - internal void InvokeOnContextBagCreated(HttpContextBagRepository contextBag) => OnContextBagCreated(contextBag); + protected virtual void OnContextBagCreated(TypedValueDictionary contextBag) { } + internal void InvokeOnContextBagCreated(TypedValueDictionary contextBag) => OnContextBagCreated(contextBag); /// /// Method that is called when an is received in the diff --git a/src/Http/Handlers/HttpServerHandlerRepository.cs b/src/Http/Handlers/HttpServerHandlerRepository.cs index 8878479..462e271 100644 --- a/src/Http/Handlers/HttpServerHandlerRepository.cs +++ b/src/Http/Handlers/HttpServerHandlerRepository.cs @@ -7,6 +7,7 @@ // File name: HttpServerHandlerRepository.cs // Repository: https://github.com/sisk-http/core +using Sisk.Core.Entity; using Sisk.Core.Routing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -56,7 +57,7 @@ private void CallEvery(Action action) internal void ServerStarting(HttpServer val) => CallEvery(handler => handler.InvokeOnServerStarting(val)); internal void ServerStarted(HttpServer val) => CallEvery(handler => handler.InvokeOnServerStarted(val)); internal void SetupRouter(Router val) => CallEvery(handler => handler.InvokeOnSetupRouter(val)); - internal void ContextBagCreated(HttpContextBagRepository val) => CallEvery(handler => handler.InvokeOnContextBagCreated(val)); + internal void ContextBagCreated(TypedValueDictionary val) => CallEvery(handler => handler.InvokeOnContextBagCreated(val)); internal void HttpRequestOpen(HttpRequest val) => CallEvery(handler => handler.InvokeOnHttpRequestOpen(val)); internal void HttpRequestClose(HttpServerExecutionResult val) => CallEvery(handler => handler.InvokeOnHttpRequestClose(val)); internal void Exception(Exception val) => CallEvery(handler => handler.InvokeOnException(val)); diff --git a/src/Http/HttpContext.cs b/src/Http/HttpContext.cs index c4774ad..ace99cf 100644 --- a/src/Http/HttpContext.cs +++ b/src/Http/HttpContext.cs @@ -32,7 +32,7 @@ public sealed class HttpContext /// /// Gets or sets a managed object that is accessed and modified by request handlers. /// - public HttpContextBagRepository RequestBag { get; set; } = new HttpContextBagRepository(); + public TypedValueDictionary RequestBag { get; set; } = new TypedValueDictionary(); /// /// Gets the context HTTP Server instance. diff --git a/src/Http/HttpContextBagRepository.cs b/src/Http/HttpContextBagRepository.cs deleted file mode 100644 index 3f13c80..0000000 --- a/src/Http/HttpContextBagRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -// The Sisk Framework source code -// Copyright (c) 2023 PROJECT PRINCIPIUM -// -// The code below is licensed under the MIT license as -// of the date of its publication, available at -// -// File name: HttpContextBagRepository.cs -// Repository: https://github.com/sisk-http/core - -using Sisk.Core.Entity; - -namespace Sisk.Core.Http; - -/// -/// Represents a repository of information stored over the lifetime of a request. -/// -public sealed class HttpContextBagRepository : TypedValueDictionary -{ -} diff --git a/src/Http/HttpKnownHeaderNames.cs b/src/Http/HttpKnownHeaderNames.cs new file mode 100644 index 0000000..3d3f68c --- /dev/null +++ b/src/Http/HttpKnownHeaderNames.cs @@ -0,0 +1,531 @@ +// The Sisk Framework source code +// Copyright (c) 2023 PROJECT PRINCIPIUM +// +// The code below is licensed under the MIT license as +// of the date of its publication, available at +// +// File name: HttpKnownHeaderNames.cs +// Repository: https://github.com/sisk-http/core + +// The source code below was forked from the official dotnet runtime +// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Sisk.Core.Http; + +/// +/// Provides most of the most commonly known HTTP headers for constants. +/// +public static class HttpKnownHeaderNames +{ + /// + /// The HTTP Accept header. + /// Specifies the media types that are acceptable for the response, allowing the client to indicate its preferences. + /// + public const string Accept = "Accept"; + + /// + /// The HTTP Accept-Charset header. + /// Indicates the character sets that are acceptable for the response, allowing the client to specify its preferred encoding. + /// + public const string AcceptCharset = "Accept-Charset"; + + /// + /// The HTTP Accept-Encoding header. + /// Specifies the content encodings that are acceptable for the response, allowing the client to indicate its preferences for compression. + /// + public const string AcceptEncoding = "Accept-Encoding"; + + /// + /// The HTTP Accept-Language header. + /// Indicates the natural languages that are preferred for the response, allowing the client to specify its language preferences. + /// + public const string AcceptLanguage = "Accept-Language"; + + /// + /// The HTTP Accept-Patch header. + /// Indicates the patch document formats that are acceptable for the response, allowing the client to specify its preferences for patching resources. + /// + public const string AcceptPatch = "Accept-Patch"; + + /// + /// The HTTP Accept-Ranges header. + /// Indicates that the server supports range requests for the resource, allowing clients to request specific byte ranges. + /// + public const string AcceptRanges = "Accept-Ranges"; + + /// + /// The HTTP Access-Control-Allow-Credentials header. + /// Indicates whether the response to the request can expose credentials, allowing cross-origin requests to include credentials. + /// + public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; + + /// + /// The HTTP Access-Control-Allow-Headers header. + /// Specifies which headers can be used when making the actual request in a cross-origin resource sharing (CORS) context. + /// + public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; + + /// + /// The HTTP Access-Control-Allow-Methods header. + /// Specifies the methods that are allowed when accessing the resource in a CORS context. + /// + public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; + + /// + /// The HTTP Access-Control-Allow-Origin header. + /// Specifies which origins are allowed to access the resource in a CORS context, helping to control cross-origin requests. + /// + public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; + + /// + /// The HTTP Access-Control-Expose-Headers header. + /// Indicates which headers can be exposed as part of the response to a cross-origin request. + /// + public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; + + /// + /// The HTTP Access-Control-Max-Age header. + /// Specifies how long the results of a preflight request can be cached, reducing the number of preflight requests made. + /// + public const string AccessControlMaxAge = "Access-Control-Max-Age"; + + /// + /// The HTTP Age header. + /// Indicates the age of the object in a cache, helping clients understand how fresh the cached response is. + /// + public const string Age = "Age"; + + /// + /// The HTTP Allow header. + /// Lists the HTTP methods that are supported by the resource, informing clients about the available actions. + /// + public const string Allow = "Allow"; + + /// + /// The HTTP Alt-Svc header. + /// Indicates that an alternative service is available for the resource, allowing clients to connect to a different server or protocol. + /// + public const string AltSvc = "Alt-Svc"; + + /// + /// The HTTP Authorization header. + /// Contains credentials for authenticating the client with the server, often used for basic or bearer token authentication. + /// + public const string Authorization = "Authorization"; + + /// + /// The HTTP Cache-Control header. + /// Directs caching mechanisms on how to cache the response, including directives for expiration and revalidation. + /// + public const string CacheControl = "Cache-Control"; + + /// + /// The HTTP Connection header. + /// Controls whether the network connection stays open after the current transaction finishes, allowing for persistent connections. + /// + public const string Connection = "Connection"; + + /// + /// The HTTP Content-Disposition header. + /// Indicates if the content should be displayed inline in the browser or treated as an attachment to be downloaded. + /// + public const string ContentDisposition = "Content-Disposition"; + + /// + /// The HTTP Content-Encoding header. + /// Specifies the encoding transformations that have been applied to the response body, such as gzip or deflate. + /// + public const string ContentEncoding = "Content-Encoding"; + + /// + /// The HTTP Content-Language header. + /// Indicates the natural language(s) of the intended audience for the response, helping clients understand the content's language. + /// + public const string ContentLanguage = "Content-Language"; + + /// + /// The HTTP Content-Length header. + /// Indicates the size of the response body in bytes, allowing the client to know how much data to expect. + /// + public const string ContentLength = "Content-Length"; + + /// + /// The HTTP Content-Location header. + /// Indicates an alternate location for the returned data, often used for redirecting clients to a different resource. + /// + public const string ContentLocation = "Content-Location"; + + /// + /// The HTTP Content-MD5 header. + /// Contains the MD5 hash of the response body, allowing clients to verify the integrity of the received data. + /// + public const string ContentMD5 = "Content-MD5"; + + /// + /// The HTTP Content-Range header. + /// Indicates the part of a document that the server is returning, used in range requests to specify byte ranges. + /// + public const string ContentRange = "Content-Range"; + + /// + /// The HTTP Content-Security-Policy header. + /// Defines security policies for the content, helping to prevent cross-site scripting (XSS) and other code injection attacks. + /// + public const string ContentSecurityPolicy = "Content-Security-Policy"; + + /// + /// The HTTP Content-Type header. + /// Indicates the media type of the resource, allowing the client to understand how to process the response body. + /// + public const string ContentType = "Content-Type"; + + /// + /// The HTTP Cookie header. + /// Contains stored HTTP cookies previously sent by the server, allowing the server to identify the client on subsequent requests. + /// + public const string Cookie = "Cookie"; + + /// + /// The HTTP Cookie2 header. + /// Used to send cookies in a more advanced format, primarily for compatibility with older versions of HTTP. + /// + public const string Cookie2 = "Cookie2"; + + /// + /// The HTTP Date header. + /// Indicates the date and time at which the message was sent, helping clients understand the freshness of the response. + /// + public const string Date = "Date"; + + /// + /// The HTTP ETag header. + /// Provides a unique identifier for a specific version of a resource, allowing clients to cache and validate resources efficiently. + /// + public const string ETag = "ETag"; + + /// + /// The HTTP Expect header. + /// Indicates that the client expects certain behaviors from the server, such as support for specific features or conditions. + /// + public const string Expect = "Expect"; + + /// + /// The HTTP Expires header. + /// Indicates the date and time after which the response is considered stale, helping clients manage caching. + /// + public const string Expires = "Expires"; + + /// + /// The HTTP From header. + /// Contains the email address of the user making the request, often used for identifying the requester. + /// + public const string From = "From"; + + /// + /// The HTTP Host header. + /// Specifies the domain name of the server and the TCP port number on which the server is listening, allowing for virtual hosting. + /// + public const string Host = "Host"; + + /// + /// The HTTP If-Match header. + /// Used to make a conditional request, allowing the client to specify that the request should only be processed if the resource matches the given ETag. + /// + public const string IfMatch = "If-Match"; + + /// + /// The HTTP If-Modified-Since header. + /// Used to make a conditional request, allowing the client to specify that the resource should only be returned if it has been modified since the given date. + /// + public const string IfModifiedSince = "If-Modified-Since"; + + /// + /// The HTTP If-None-Match header. + /// Used to make a conditional request, allowing the client to specify that the resource should only be returned if it does not match the given ETag. + /// + public const string IfNoneMatch = "If-None-Match"; + + /// + /// The HTTP If-Range header. + /// Used to make a conditional range request, allowing the client to specify that the range should only be returned if the resource has not changed. + /// + public const string IfRange = "If-Range"; + + /// + /// The HTTP If-Unmodified-Since header. + /// Used to make a conditional request, allowing the client to specify that the resource should only be returned if it has not been modified since the given date. + /// + public const string IfUnmodifiedSince = "If-Unmodified-Since"; + + /// + /// The HTTP Keep-Alive header. + /// Used to specify parameters for persistent connections, allowing the client and server to maintain an open connection for multiple requests. + /// + public const string KeepAlive = "Keep-Alive"; + + /// + /// The HTTP Last-Modified header. + /// Indicates the date and time at which the resource was last modified, helping clients determine if they need to refresh their cached version. + /// + public const string LastModified = "Last-Modified"; + + /// + /// The HTTP Link header. + /// Used to provide relationships between the current resource and other resources, often used for navigation and linking. + /// + public const string Link = "Link"; + + /// + /// The HTTP Location header. + /// Used in redirection responses to indicate the URL to which the client should redirect. + /// + public const string Location = "Location"; + + /// + /// The HTTP Max-Forwards header. + /// Used in OPTIONS requests to limit the number of times the request can be forwarded by proxies. + /// + public const string MaxForwards = "Max-Forwards"; + + /// + /// The HTTP Origin header. + /// Indicates the origin of the request, helping servers implement CORS and manage cross-origin requests. + /// + public const string Origin = "Origin"; + + /// + /// The HTTP P3P header. + /// Used to indicate the privacy policy of the server, allowing clients to understand how their data will be handled. + /// + public const string P3P = "P3P"; + + /// + /// The HTTP Pragma header. + /// Used to include implementation-specific directives that might apply to any recipient along the request/response chain. + /// + public const string Pragma = "Pragma"; + + /// + /// The HTTP Proxy-Authenticate header. + /// Used by a proxy server to request authentication from the client, indicating the authentication method required. + /// + public const string ProxyAuthenticate = "Proxy-Authenticate"; + + /// + /// The HTTP Proxy-Authorization header. + /// Contains credentials for authenticating the client with a proxy server, allowing access to the requested resource. + /// + public const string ProxyAuthorization = "Proxy-Authorization"; + + /// + /// The HTTP Proxy-Connection header. + /// Used to control whether the network connection to the proxy server should be kept open after the current transaction. + /// + public const string ProxyConnection = "Proxy-Connection"; + + /// + /// The HTTP Public-Key-Pins header. + /// Used to prevent man-in-the-middle attacks by specifying which public keys are valid for the server's certificate. + /// + public const string PublicKeyPins = "Public-Key-Pins"; + + /// + /// The HTTP Range header. + /// Used to request a specific range of bytes from a resource, allowing clients to download large files in parts. + /// + public const string Range = "Range"; + + /// + /// The HTTP Referer header. + /// Indicates the URL of the resource from which the request originated, helping servers understand the source of traffic. + /// + public const string Referer = "Referer"; + + /// + /// The HTTP Retry-After header. + /// Indicates how long the client should wait before making a follow-up request, often used in rate limiting scenarios. + /// + public const string RetryAfter = "Retry-After"; + + /// + /// The HTTP Sec-WebSocket-Accept header. + /// Used in the WebSocket handshake to confirm the server's acceptance of the connection request. + /// + public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; + + /// + /// The HTTP Sec-WebSocket-Extensions header. + /// Used to negotiate WebSocket extensions during the handshake, allowing for additional features and capabilities. + /// + public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; + + /// + /// The HTTP Sec-WebSocket-Key header. + /// Contains a base64-encoded value used to establish a WebSocket connection, ensuring the request is valid. + /// + public const string SecWebSocketKey = "Sec-WebSocket-Key"; + + /// + /// The HTTP Sec-WebSocket-Protocol header. + /// Used to specify subprotocols that the client wishes to use during the WebSocket connection. + /// + public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; + + /// + /// The HTTP Sec-WebSocket-Version header. + /// Indicates the version of the WebSocket protocol that the client wishes to use. + /// + public const string SecWebSocketVersion = "Sec-WebSocket-Version"; + + /// + /// The HTTP Server header. + /// Contains information about the server software handling the request, often used for informational purposes. + /// + public const string Server = "Server"; + + /// + /// The HTTP Set-Cookie header. + /// Used to send cookies from the server to the client, allowing the server to store state information on the client. + /// + public const string SetCookie = "Set-Cookie"; + + /// + /// The HTTP Set-Cookie2 header. + /// Used to send cookies in a more advanced format, primarily for compatibility with older versions of HTTP. + /// + public const string SetCookie2 = "Set-Cookie2"; + + /// + /// The HTTP Strict-Transport-Security header. + /// Enforces secure (HTTPS) connections to the server, helping to prevent man-in-the-middle attacks. + /// + public const string StrictTransportSecurity = "Strict-Transport-Security"; + + /// + /// The HTTP TE header. + /// Indicates the transfer encodings that are acceptable for the response, allowing for content negotiation. + /// + public const string TE = "TE"; + + /// + /// The HTTP TSV header. + /// Used to indicate the type of data being sent in a transaction, often used in specific applications or protocols. + /// + public const string TSV = "TSV"; + + /// + /// The HTTP Trailer header. + /// Indicates that the sender will include additional fields in the message trailer, which can be used for metadata. + /// + public const string Trailer = "Trailer"; + + /// + /// The HTTP Transfer-Encoding header. + /// Specifies the form of encoding used to safely transfer the payload body to the user. + /// + public const string TransferEncoding = "Transfer-Encoding"; + + /// + /// The HTTP Upgrade header. + /// Indicates that the client prefers to upgrade to a different protocol, such as switching from HTTP/1.1 to HTTP/2. + /// + public const string Upgrade = "Upgrade"; + + /// + /// The HTTP Upgrade-Insecure-Requests header. + /// Indicates that the client prefers to receive an upgraded version of the resource over HTTPS instead of HTTP. + /// + public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; + + /// + /// The HTTP User-Agent header. + /// Contains information about the user agent (browser or application) making the request, including its version and platform. + /// + public const string UserAgent = "User-Agent"; + + /// + /// The HTTP Vary header. + /// Indicates that the response varies based on the value of the specified request headers, allowing for content negotiation. + /// + public const string Vary = "Vary"; + + /// + /// The HTTP Via header. + /// Used to track message forwards and proxies, indicating the intermediate protocols and recipients involved in the request/response chain. + /// + public const string Via = "Via"; + + /// + /// The HTTP WWW-Authenticate header. + /// Used in response to a request for authentication, indicating the authentication method that should be used to access the resource. + /// + public const string WWWAuthenticate = "WWW-Authenticate"; + + /// + /// The HTTP Warning header. + /// Provides additional information about the status or transformation of a message, often used for caching and validation. + /// + public const string Warning = "Warning"; + + /// + /// The HTTP X-AspNet-Version header. + /// Indicates the version of ASP.NET that the server is using to process the request. + /// + public const string XAspNetVersion = "X-AspNet-Version"; + + /// + /// The HTTP X-Content-Duration header. + /// Specifies the duration of the content in seconds, often used for media files. + /// + public const string XContentDuration = "X-Content-Duration"; + + /// + /// The HTTP X-Content-Type-Options header. + /// Used to prevent MIME type sniffing, ensuring that the browser respects the declared content type. + /// + public const string XContentTypeOptions = "X-Content-Type-Options"; + + /// + /// The HTTP X-Frame-Options header. + /// Used to control whether a browser should be allowed to render a page in a iframe, frame, embed or object tag, helping to prevent clickjacking attacks. + /// + public const string XFrameOptions = "X-Frame-Options"; + + /// + /// The HTTP X-MSEdge-Ref header. + /// Used by Microsoft Edge to provide information about the request context, often for analytics and debugging purposes. + /// + public const string XMSEdgeRef = "X-MSEdge-Ref"; + + /// + /// The HTTP X-Powered-By header. + /// Indicates the technology or framework that powers the web application, often used for informational purposes. + /// + public const string XPoweredBy = "X-Powered-By"; + + /// + /// The HTTP X-Forwarded-Host header. + /// Used to identify the original host requested by the client in the Host HTTP request header, often used in proxy setups. + /// + public const string XForwardedHost = "X-Forwarded-Host"; + + /// + /// The HTTP X-Forwarded-For header. + /// Used to identify the originating IP address of a client connecting to a web server through an HTTP proxy or load balancer. + /// + public const string XForwardedFor = "X-Forwarded-For"; + + /// + /// The HTTP X-Request-ID header. + /// Used to uniquely identify a request for tracking and debugging purposes, often generated by the client or server. + /// + public const string XRequestID = "X-Request-ID"; + + /// + /// The HTTP X-UA-Compatible header. + /// Used to specify the document mode that Internet Explorer should use to render the page, helping to ensure compatibility with older versions. + /// + public const string XUACompatible = "X-UA-Compatible"; +} \ No newline at end of file diff --git a/src/Http/HttpRequest.cs b/src/Http/HttpRequest.cs index 0e5b9c1..786b95e 100644 --- a/src/Http/HttpRequest.cs +++ b/src/Http/HttpRequest.cs @@ -45,8 +45,7 @@ public sealed class HttpRequest private NameValueCollection? cookies = null; private StringValueCollection? query = null; private StringValueCollection? form = null; - - private IPAddress remoteAddr; + private IPAddress? remoteAddr; private int currentFrame = 0; @@ -60,18 +59,6 @@ internal HttpRequest( listenerResponse = context.Response; listenerRequest = context.Request; RequestedAt = DateTime.Now; - - // the listenerRequest.RemoteEndPoint property is disposed after the - // response is sent to the server, resulting in an null exception when - // getting it in an OnConnectionClose handler - if (contextServerConfiguration.ForwardingResolver is { } fr) - { - remoteAddr = fr.OnResolveClientAddress(this, listenerRequest.RemoteEndPoint); - } - else - { - remoteAddr = new IPAddress(listenerRequest.RemoteEndPoint.Address.GetAddressBytes()); - } } internal string mbConvertCodepage(string input, Encoding inEnc, Encoding outEnc) @@ -201,7 +188,7 @@ public NameValueCollection Cookies /// /// This property is an shortcut for property. /// - public HttpContextBagRepository Bag => Context.RequestBag; + public TypedValueDictionary Bag => Context.RequestBag; /// /// Get the requested host header with the port from this HTTP request. @@ -216,11 +203,11 @@ public string Authority /// public string Path { - get => listenerRequest.Url?.AbsolutePath ?? "/"; + get => HttpUtility.UrlDecode(listenerRequest.Url?.AbsolutePath ?? "/"); } /// - /// Gets the full HTTP request path with the query string. + /// Gets the raw, full HTTP request path with the query string. /// public string FullPath { @@ -307,7 +294,21 @@ public StringValueCollection Query /// public IPAddress RemoteAddress { - get => remoteAddr; + get + { + if (remoteAddr is null) + { + if (contextServerConfiguration.ForwardingResolver is { } fr) + { + remoteAddr = fr.OnResolveClientAddress(this, listenerRequest.RemoteEndPoint); + } + else + { + remoteAddr = new IPAddress(listenerRequest.RemoteEndPoint.Address.GetAddressBytes()); + } + } + return remoteAddr; + } } /// diff --git a/src/Http/ListeningPort.cs b/src/Http/ListeningPort.cs index 24cfdb0..9f25de1 100644 --- a/src/Http/ListeningPort.cs +++ b/src/Http/ListeningPort.cs @@ -111,34 +111,22 @@ public ListeningPort(bool secure, string hostname, ushort port) /// The URI component that will be parsed to the listening port format. public ListeningPort(string uri) { - int schemeIndex = uri.IndexOf(":"); - if (schemeIndex == -1) throw new ArgumentException(SR.ListeningPort_Parser_UndefinedScheme); - int portIndex = uri.IndexOf(":", schemeIndex + 3); - if (portIndex == -1) throw new ArgumentException(SR.ListeningPort_Parser_UndefinedPort); - int endIndex = uri.IndexOf("/", schemeIndex + 3); - if (endIndex == -1 || !uri.EndsWith('/')) throw new ArgumentException(SR.ListeningPort_Parser_UriNotTerminatedSlash); - - string schemePart = uri.Substring(0, schemeIndex); - string hostnamePart = uri.Substring(schemeIndex + 3, portIndex - (schemeIndex + 3)); - string portPart = uri.Substring(portIndex + 1, endIndex - (portIndex + 1)); - - if (schemePart == "http") + if (ushort.TryParse(uri, out ushort port)) { - Secure = false; + Hostname = "localhost"; + Port = port; + Secure = port == 443; } - else if (schemePart == "https") + else if (Uri.TryCreate(uri, UriKind.RelativeOrAbsolute, out var uriResult)) { - Secure = true; + Hostname = uriResult.Host; + Port = (ushort)uriResult.Port; + Secure = string.Compare(uriResult.Scheme, "https", true) == 0; } else { - throw new ArgumentException(SR.ListeningPort_Parser_InvalidScheme); + throw new ArgumentException(SR.ListeningPort_Parser_InvalidInput); } - - if (!ushort.TryParse(portPart, out ushort port)) throw new ArgumentException(SR.ListeningPort_Parser_InvalidPort); - - Port = port; - Hostname = hostnamePart; } /// diff --git a/src/Http/Streams/HttpResponseStream.cs b/src/Http/Streams/HttpResponseStream.cs index 3935ef0..e4d6c0a 100644 --- a/src/Http/Streams/HttpResponseStream.cs +++ b/src/Http/Streams/HttpResponseStream.cs @@ -7,7 +7,6 @@ // File name: HttpResponseStream.cs // Repository: https://github.com/sisk-http/core -using Sisk.Core.Internal; using System.Net; namespace Sisk.Core.Http.Streams; @@ -73,20 +72,25 @@ public void SetContentLength(long contentLength) /// /// The HTTP header name. /// The HTTP header value. - public void SetHeader(string headerName, string value) + public void SetHeader(string headerName, object? value) { + string? _value = value?.ToString(); + if (_value is null) + { + return; + } if (hasSentData) throw new InvalidOperationException(SR.Httpserver_Commons_HeaderAfterContents); if (string.Compare(headerName, HttpKnownHeaderNames.ContentLength, true) == 0) { - SetContentLength(long.Parse(value)); + SetContentLength(long.Parse(_value)); } else if (string.Compare(headerName, HttpKnownHeaderNames.ContentType, true) == 0) { - listenerResponse.ContentType = value; + listenerResponse.ContentType = _value; } else { - listenerResponse.AddHeader(headerName, value); + listenerResponse.AddHeader(headerName, _value); } } diff --git a/src/Internal/HttpKnownHeaderNames.cs b/src/Internal/HttpKnownHeaderNames.cs deleted file mode 100644 index 6927751..0000000 --- a/src/Internal/HttpKnownHeaderNames.cs +++ /dev/null @@ -1,104 +0,0 @@ -// The Sisk Framework source code -// Copyright (c) 2023 PROJECT PRINCIPIUM -// -// The code below is licensed under the MIT license as -// of the date of its publication, available at -// -// File name: HttpKnownHeaderNames.cs -// Repository: https://github.com/sisk-http/core - -// The source code below was forked from the official dotnet runtime -// -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Sisk.Core.Internal; - -internal static partial class HttpKnownHeaderNames -{ - public const string Accept = "Accept"; - public const string AcceptCharset = "Accept-Charset"; - public const string AcceptEncoding = "Accept-Encoding"; - public const string AcceptLanguage = "Accept-Language"; - public const string AcceptPatch = "Accept-Patch"; - public const string AcceptRanges = "Accept-Ranges"; - public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; - public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; - public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; - public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; - public const string AccessControlMaxAge = "Access-Control-Max-Age"; - public const string Age = "Age"; - public const string Allow = "Allow"; - public const string AltSvc = "Alt-Svc"; - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string Connection = "Connection"; - public const string ContentDisposition = "Content-Disposition"; - public const string ContentEncoding = "Content-Encoding"; - public const string ContentLanguage = "Content-Language"; - public const string ContentLength = "Content-Length"; - public const string ContentLocation = "Content-Location"; - public const string ContentMD5 = "Content-MD5"; - public const string ContentRange = "Content-Range"; - public const string ContentSecurityPolicy = "Content-Security-Policy"; - public const string ContentType = "Content-Type"; - public const string Cookie = "Cookie"; - public const string Cookie2 = "Cookie2"; - public const string Date = "Date"; - public const string ETag = "ETag"; - public const string Expect = "Expect"; - public const string Expires = "Expires"; - public const string From = "From"; - public const string Host = "Host"; - public const string IfMatch = "If-Match"; - public const string IfModifiedSince = "If-Modified-Since"; - public const string IfNoneMatch = "If-None-Match"; - public const string IfRange = "If-Range"; - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - public const string KeepAlive = "Keep-Alive"; - public const string LastModified = "Last-Modified"; - public const string Link = "Link"; - public const string Location = "Location"; - public const string MaxForwards = "Max-Forwards"; - public const string Origin = "Origin"; - public const string P3P = "P3P"; - public const string Pragma = "Pragma"; - public const string ProxyAuthenticate = "Proxy-Authenticate"; - public const string ProxyAuthorization = "Proxy-Authorization"; - public const string ProxyConnection = "Proxy-Connection"; - public const string PublicKeyPins = "Public-Key-Pins"; - public const string Range = "Range"; - public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. - public const string RetryAfter = "Retry-After"; - public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; - public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; - public const string SecWebSocketKey = "Sec-WebSocket-Key"; - public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; - public const string SecWebSocketVersion = "Sec-WebSocket-Version"; - public const string Server = "Server"; - public const string SetCookie = "Set-Cookie"; - public const string SetCookie2 = "Set-Cookie2"; - public const string StrictTransportSecurity = "Strict-Transport-Security"; - public const string TE = "TE"; - public const string TSV = "TSV"; - public const string Trailer = "Trailer"; - public const string TransferEncoding = "Transfer-Encoding"; - public const string Upgrade = "Upgrade"; - public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; - public const string UserAgent = "User-Agent"; - public const string Vary = "Vary"; - public const string Via = "Via"; - public const string WWWAuthenticate = "WWW-Authenticate"; - public const string Warning = "Warning"; - public const string XAspNetVersion = "X-AspNet-Version"; - public const string XContentDuration = "X-Content-Duration"; - public const string XContentTypeOptions = "X-Content-Type-Options"; - public const string XFrameOptions = "X-Frame-Options"; - public const string XMSEdgeRef = "X-MSEdge-Ref"; - public const string XPoweredBy = "X-Powered-By"; - public const string XForwardedHost = "X-Forwarded-Host"; - public const string XForwardedFor = "X-Forwarded-For"; - public const string XRequestID = "X-Request-ID"; - public const string XUACompatible = "X-UA-Compatible"; -} \ No newline at end of file diff --git a/src/Internal/PathUtility.cs b/src/Internal/PathUtility.cs index 636016b..5b83469 100644 --- a/src/Internal/PathUtility.cs +++ b/src/Internal/PathUtility.cs @@ -45,4 +45,58 @@ public static string CombinePaths(params string[] paths) return sb.ToString(); } + + public static string NormalizedCombine(bool allowReturn, char environmentPathChar, ReadOnlySpan paths) + { + if (paths.Length == 0) return ""; + + bool startsWithSepChar = paths[0].StartsWith("/") || paths[0].StartsWith("\\"); + List tokens = new List(); + + for (int ip = 0; ip < paths.Length; ip++) + { + string path = paths[ip] + ?? throw new ArgumentNullException($"The path string at index {ip} is null."); + + string normalizedPath = path + .Replace('/', environmentPathChar) + .Replace('\\', environmentPathChar) + .Trim(environmentPathChar); + + string[] pathIdentities = normalizedPath.Split( + environmentPathChar, + StringSplitOptions.RemoveEmptyEntries + ); + + tokens.AddRange(pathIdentities); + } + + Stack insertedIndexes = new Stack(); + StringBuilder pathBuilder = new StringBuilder(); + foreach (string token in tokens) + { + if (token == ".") + { + continue; + } + else if (token == "..") + { + if (allowReturn) + { + pathBuilder.Length = insertedIndexes.Pop(); + } + } + else + { + insertedIndexes.Push(pathBuilder.Length); + pathBuilder.Append(token); + pathBuilder.Append(environmentPathChar); + } + } + + if (startsWithSepChar) + pathBuilder.Insert(0, environmentPathChar); + + return pathBuilder.ToString().TrimEnd(environmentPathChar); + } } diff --git a/src/Internal/SR.cs b/src/Internal/SR.cs index a9e9932..e3b6b27 100644 --- a/src/Internal/SR.cs +++ b/src/Internal/SR.cs @@ -39,11 +39,7 @@ static partial class SR public const string ListeningHostRepository_Duplicate = "This ListeningHost has already been defined in this collection with identical definitions."; - public const string ListeningPort_Parser_UndefinedScheme = "Scheme was not defined in the URI."; - public const string ListeningPort_Parser_UndefinedPort = "The URI port must be explicitly defined."; - public const string ListeningPort_Parser_UriNotTerminatedSlash = "The URI must terminate with /."; - public const string ListeningPort_Parser_InvalidScheme = "The URI scheme must be http or https."; - public const string ListeningPort_Parser_InvalidPort = "The URI port is invalid."; + public const string ListeningPort_Parser_InvalidInput = "Invalid ListeningPort syntax."; public const string LogStream_NotBuffering = "This LogStream is not buffering. To peek lines, call StartBuffering() first."; public const string LogStream_NoOutput = "No output writter was set up for this LogStream."; diff --git a/src/Routing/AsyncRequestHandler.cs b/src/Routing/AsyncRequestHandler.cs index da2c1cb..384d48f 100644 --- a/src/Routing/AsyncRequestHandler.cs +++ b/src/Routing/AsyncRequestHandler.cs @@ -14,13 +14,8 @@ namespace Sisk.Core.Routing; /// /// Represents a class that implements and its execution method is asynchronous. /// -public abstract class AsyncRequestHandler : IRequestHandler +public abstract class AsyncRequestHandler : RequestHandler { - /// - /// Gets or sets when this RequestHandler should run. - /// - public abstract RequestHandlerExecutionMode ExecutionMode { get; init; } - /// /// This method is called by the before executing a request when the instantiates an object that implements this interface. If it returns /// a object, the route action is not called and all execution of the route is stopped. If it returns "null", the execution is continued. @@ -29,17 +24,9 @@ public abstract class AsyncRequestHandler : IRequestHandler /// The HTTP request context. It may contain information from other . public abstract Task ExecuteAsync(HttpRequest request, HttpContext context); - /// - /// Returns an null reference, which points to the next request handler or route action. - /// - public HttpResponse? Next() - { - return null; - } - /// /// - public HttpResponse? Execute(HttpRequest request, HttpContext context) + public sealed override HttpResponse? Execute(HttpRequest request, HttpContext context) { return ExecuteAsync(request, context).GetAwaiter().GetResult(); } diff --git a/src/Routing/Router.cs b/src/Routing/Router.cs index 54f4ac9..5f44ffc 100644 --- a/src/Routing/Router.cs +++ b/src/Routing/Router.cs @@ -58,6 +58,40 @@ public static string CombinePaths(params string[] paths) return PathUtility.CombinePaths(paths); } + /// + /// Normalizes and combines the specified file-system paths into one. + /// + /// Specifies if relative paths should be merged and ".." returns should be respected. + /// Specifies the path separator character. + /// Specifies the array of paths to combine. + public static string FilesystemCombinePaths(bool allowRelativeReturn, char separator, ReadOnlySpan paths) + { + return PathUtility.NormalizedCombine(allowRelativeReturn, separator, paths); + } + + /// + /// Normalizes and combines the specified file-system paths into one, using the default environment directory separator char. + /// + /// Specifies the array of paths to combine. + public static string FilesystemCombinePaths(params string[] paths) + { + return PathUtility.NormalizedCombine(false, Path.DirectorySeparatorChar, paths); + } + + /// + /// Normalize the given path to use the specified directory separator, trim the last separator and + /// remove empty entries. + /// + /// The path to normalize. + /// The directory separator. + public static string NormalizePath(string path, char directorySeparator = '/') + { + string[] parts = path.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + string result = string.Join(directorySeparator, parts); + if (path.StartsWith('/') || path.StartsWith('\\')) result = directorySeparator + result; + return result; + } + /// /// Gets an boolean indicating where this is read-only or not. /// diff --git a/src/Sisk.Core.csproj b/src/Sisk.Core.csproj index c8eb8f5..935f719 100644 --- a/src/Sisk.Core.csproj +++ b/src/Sisk.Core.csproj @@ -89,8 +89,4 @@ - - - - \ No newline at end of file