Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
CypherPotato committed Jun 25, 2024
1 parent da37622 commit 2fe53f0
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 99 deletions.
52 changes: 52 additions & 0 deletions src/Http/ForwardingResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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: ForwardingResolver.cs
// Repository: https://github.com/sisk-http/core

using System.Net;

namespace Sisk.Core.Http;

/// <summary>
/// Provides HTTP forwarding resolving methods that can be used to resolving the client remote
/// address, host and protocol of a proxy, load balancer or CDN, through the HTTP request.
/// </summary>
public class ForwardingResolver
{
/// <summary>
/// Method that is called when resolving the IP address of the client in the request.
/// </summary>
/// <param name="request">The <see cref="HttpRequest"/> object which contains parameters of the request.</param>
/// <param name="connectingEndpoint">The original connecting endpoint.</param>
/// <returns></returns>
public virtual IPAddress OnResolveClientAddress(HttpRequest request, IPEndPoint connectingEndpoint)
{
return connectingEndpoint.Address;
}

/// <summary>
/// Method that is called when resolving the client request host.
/// </summary>
/// <param name="request">The <see cref="HttpRequest"/> object which contains parameters of the request.</param>
/// <param name="requestedHost">The original requested host.</param>
/// <returns></returns>
public virtual string OnResolveRequestHost(HttpRequest request, string requestedHost)
{
return requestedHost;
}

/// <summary>
/// Method that is called when resolving whether the HTTP request is using HTTPS or HTTP.
/// </summary>
/// <param name="request">The <see cref="HttpRequest"/> object which contains parameters of the request.</param>
/// <param name="isSecure">The original security state of the request.</param>
/// <returns></returns>
public virtual bool OnResolveSecureConnection(HttpRequest request, bool isSecure)
{
return isSecure;
}
}
19 changes: 19 additions & 0 deletions src/Http/Hosting/HttpServerHostContextBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ public HttpServerHostContextBuilder UseFlags(HttpServerFlags flags)
return this;
}

/// <summary>
/// This method is a shortcut for setting <see cref="HttpServerConfiguration.ForwardingResolver"/>.
/// </summary>
/// <param name="resolver">The <see cref="ForwardingResolver"/> object.</param>
public HttpServerHostContextBuilder UseForwardingResolver(ForwardingResolver resolver)
{
_context.ServerConfiguration.ForwardingResolver = resolver;
return this;
}

/// <summary>
/// This method is a shortcut for setting <see cref="HttpServerConfiguration.ForwardingResolver"/>.
/// </summary>
/// <typeparam name="TForwardingResolver">The type which implements <see cref="ForwardingResolver"/>.</typeparam>
public HttpServerHostContextBuilder UseForwardingResolver<TForwardingResolver>() where TForwardingResolver : ForwardingResolver, new()
{
return UseForwardingResolver(new TForwardingResolver());
}

/// <summary>
/// Calls an action that has the HTTP server configuration as an argument.
/// </summary>
Expand Down
83 changes: 15 additions & 68 deletions src/Http/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ internal HttpRequestException(string message) : base(message) { }
public sealed class HttpRequest
{
internal HttpServer baseServer;
internal ListeningHost hostContext;
private readonly HttpServerConfiguration contextServerConfiguration;
private readonly HttpListenerResponse listenerResponse;
private readonly HttpListenerRequest listenerRequest;
Expand All @@ -47,14 +46,11 @@ public sealed class HttpRequest
private StringValueCollection? form = null;

private IPAddress? remoteAddr;
private string? remoteHost;
private bool isSsl;

private int currentFrame = 0;

internal HttpRequest(
HttpServer server,
ListeningHost host,
HttpListenerContext context)
{
this.context = context;
Expand All @@ -63,7 +59,6 @@ internal HttpRequest(
listenerResponse = context.Response;
listenerRequest = context.Request;
RequestedAt = DateTime.Now;
hostContext = host;
}

internal string mbConvertCodepage(string input, Encoding inEnc, Encoding outEnc)
Expand Down Expand Up @@ -110,30 +105,14 @@ public bool IsSecure
{
get
{
if (contextServerConfiguration.ResolveForwardedProtocol)
if (contextServerConfiguration.ForwardingResolver is { } fr)
{
string? fwdProtocol = listenerRequest.Headers["X-Forwarded-Proto"] ??
listenerRequest.Headers["X-Forwarded-Protocol"] ??
listenerRequest.Headers["X-Url-Scheme"];

if (fwdProtocol is not null)
{
isSsl = string.Compare(fwdProtocol, "https", true) == 0;
}
else
{
string? fwdSsl = listenerRequest.Headers["X-Forwarded-Ssl"] ??
listenerRequest.Headers["Front-End-Https"];

isSsl = string.Compare(fwdSsl, "on", true) == 0;
}
return fr.OnResolveSecureConnection(this, listenerRequest.IsSecureConnection);
}
else
{
isSsl = listenerRequest.IsSecureConnection;
return listenerRequest.IsSecureConnection;
}

return isSsl;
}
}

Expand Down Expand Up @@ -220,30 +199,7 @@ public NameValueCollection Cookies
/// <summary>
/// Get the requested host header (without port) from this HTTP request.
/// </summary>
public string Host
{
get
{
if (contextServerConfiguration.ResolveForwardedOriginHost)
{
string? forwardedHost = listenerRequest.Headers[HttpKnownHeaderNames.XForwardedHost];
if (forwardedHost != null)
{
remoteHost = forwardedHost;
}
else
{
remoteHost = listenerRequest.Url!.Host;
}
}
else
{
remoteHost = listenerRequest.Url!.Host;
}

return remoteHost;
}
}
public string? Host { get; internal set; }

/// <summary>
/// Gets the managed object which holds data for an entire HTTP session.
Expand Down Expand Up @@ -359,26 +315,9 @@ public IPAddress RemoteAddress
{
get
{
if (contextServerConfiguration.ResolveForwardedOriginAddress)
if (contextServerConfiguration.ForwardingResolver is { } fr)
{
string? forwardedIp = listenerRequest.Headers[HttpKnownHeaderNames.XForwardedFor];
if (forwardedIp != null)
{
string forwardedIpLiteralStr = forwardedIp.Contains(',') ? forwardedIp.Substring(forwardedIp.IndexOf(',') + 1) : forwardedIp;
bool ok = IPAddress.TryParse(forwardedIpLiteralStr, out IPAddress? forwardedAddress);
if (!ok || forwardedAddress is null)
{
throw new HttpRequestException(SR.HttpRequest_InvalidForwardedIpAddress);
}
else
{
remoteAddr = forwardedAddress;
}
}
else
{
remoteAddr = new IPAddress(listenerRequest.RemoteEndPoint.Address.GetAddressBytes());
}
remoteAddr = fr.OnResolveClientAddress(this, listenerRequest.RemoteEndPoint);
}
else
{
Expand Down Expand Up @@ -422,7 +361,7 @@ public StringValueCollection GetFormContent()
/// <summary>
/// Gets the raw HTTP request message from the socket.
/// </summary>
public string GetRawHttpRequest(bool includeBody = true)
public string GetRawHttpRequest(bool includeBody = true, bool appendExtraInfo = false)
{
StringBuilder sb = new StringBuilder();
// Method and path
Expand All @@ -433,6 +372,14 @@ public string GetRawHttpRequest(bool includeBody = true)
sb.Append(listenerRequest.ProtocolVersion.Minor + "\n");

// Headers
if (appendExtraInfo)
{
sb.AppendLine($":remote-ip: {RemoteAddress} (was {listenerRequest.RemoteEndPoint})");
sb.AppendLine($":host: {Host} (was {listenerRequest.UserHostName})");
sb.AppendLine($":date: {RequestedAt:s}");
sb.AppendLine($":request-id: {RequestId}");
sb.AppendLine($":request-proto: {(IsSecure ? "https" : "http")}");
}
foreach (string hName in Headers)
{
string hValue = Headers[hName]!;
Expand Down
8 changes: 5 additions & 3 deletions src/Http/HttpServerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,20 @@ public class HttpServerConfiguration : IDisposable
/// Gets or sets whether the HTTP server should resolve remote (IP) addresses by the X-Forwarded-For header. This option is useful if you are using
/// Sisk through a reverse proxy or load balancer.
/// </summary>
[Obsolete("This property is deprecated and should not be used. Use ForwardingResolver property instead.")]
public bool ResolveForwardedOriginAddress { get; set; } = false;

/// <summary>
/// Gets or sets whether the HTTP server should resolve remote forwarded hosts by the header X-Forwarded-Host.
/// </summary>
[Obsolete("This property is deprecated and should not be used. Use ForwardingResolver property instead.")]
public bool ResolveForwardedOriginHost { get; set; } = false;

/// <summary>
/// Gets or sets whether the HTTP server should resolve the protocol (HTTP or HTTPS) used by the
/// client to reach the current HTTP server through an proxy or load balancer.
/// Gets or sets an object that is responsible for resolving the
/// client address, host and protocol of a proxy, load balancer or CDN, through the HTTP request.
/// </summary>
public bool ResolveForwardedProtocol { get; set; } = false;
public ForwardingResolver? ForwardingResolver { get; set; }

/// <summary>
/// Gets or sets the default encoding for sending and decoding messages.
Expand Down
19 changes: 8 additions & 11 deletions src/Http/HttpServer__Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,17 @@ private void ProcessRequest(HttpListenerContext context)
return;
}

string dnsSafeHost = connectingUri.DnsSafeHost;
string? forwardedHost = baseRequest.Headers[HttpKnownHeaderNames.XForwardedHost];
if (ServerConfiguration.ResolveForwardedOriginHost && forwardedHost != null)
request = new HttpRequest(this, context);
string dnsSafeHost = baseRequest.UserHostName;

if (ServerConfiguration.ForwardingResolver is ForwardingResolver fr)
{
dnsSafeHost = forwardedHost;
dnsSafeHost = fr.OnResolveRequestHost(request, dnsSafeHost);
}

// detect the listening host for this listener
ListeningHost? matchedListeningHost = _onlyListeningHost
?? ServerConfiguration.ListeningHosts.GetRequestMatchingListeningHost(dnsSafeHost, baseRequest.LocalEndPoint.Port);
?? ServerConfiguration.ListeningHosts.GetRequestMatchingListeningHost(dnsSafeHost, baseRequest.LocalEndPoint.Port);

if (matchedListeningHost is null)
{
Expand All @@ -194,16 +195,12 @@ private void ProcessRequest(HttpListenerContext context)
return;
}

request = new HttpRequest(this, matchedListeningHost, context);
srContext = new HttpContext(this, request, null, matchedListeningHost);

request.Host = dnsSafeHost;
request.Context = srContext;
executionResult.Request = request;

if (ServerConfiguration.ResolveForwardedOriginAddress)
{
otherParty = request.RemoteAddress;
}
otherParty = request.RemoteAddress;

if (!matchedListeningHost.CanListen)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Streams/HttpRequestEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ internal HttpRequestEventSource(string? identifier, HttpListenerResponse res, Ht
res.AddHeader("Content-Type", "text/event-stream");
res.AddHeader("X-Powered-By", HttpServer.PoweredBy);

HttpServer.SetCorsHeaders(host.baseServer.ServerConfiguration.Flags, req, host.hostContext.CrossOriginResourceSharingPolicy, res);
HttpServer.SetCorsHeaders(host.baseServer.ServerConfiguration.Flags, req, host.Context.ListeningHost.CrossOriginResourceSharingPolicy, res);
}

private void keepAliveTask()
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Streams/HttpResponseStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal HttpResponseStream(HttpListenerResponse listenerResponse, HttpListenerR
{
this.listenerResponse = listenerResponse ?? throw new ArgumentNullException(nameof(listenerResponse));
ResponseStream = new ResponseStreamWriter(listenerResponse.OutputStream, this);
HttpServer.SetCorsHeaders(host.baseServer.ServerConfiguration.Flags, listenerRequest, host.hostContext.CrossOriginResourceSharingPolicy, listenerResponse);
HttpServer.SetCorsHeaders(host.baseServer.ServerConfiguration.Flags, listenerRequest, host.Context.ListeningHost.CrossOriginResourceSharingPolicy, listenerResponse);
}

/// <summary>
Expand Down
14 changes: 7 additions & 7 deletions src/Internal/HttpStringInternals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,20 @@ public static PathMatchResult IsPathMatch(string pathPattern, string requestPath
public static bool IsDnsMatch(string wildcardPattern, string subject)
{
StringComparison comparer = StringComparison.OrdinalIgnoreCase;
wildcardPattern = wildcardPattern.Trim();
subject = subject.Trim();

if (string.IsNullOrWhiteSpace(wildcardPattern))
int portSeparatorPosition = subject.IndexOf(':');
if (portSeparatorPosition > 0)
{
return false;
subject = subject[0..portSeparatorPosition];
}

if (subject.StartsWith(wildcardPattern.Replace("*.", "")))
int wildcardCount = wildcardPattern.Count(x => x == '*');
if (wildcardPattern.StartsWith("*.") && wildcardCount == 1)
{
return true;
if (string.Compare(subject, wildcardPattern[2..], comparer) == 0)
return true;
}

int wildcardCount = wildcardPattern.Count(x => x.Equals('*'));
if (wildcardCount <= 0)
{
return subject.Equals(wildcardPattern, comparer);
Expand Down
4 changes: 0 additions & 4 deletions src/Internal/ServiceProvider/JsonConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ public void ReadConfiguration(ConfigurationContext prov)

if (config.Server != null)
{
prov.Host.ServerConfiguration.ResolveForwardedOriginAddress = config.Server.ResolveForwardedOriginAddress;
prov.Host.ServerConfiguration.ResolveForwardedOriginHost = config.Server.ResolveForwardedOriginHost;
prov.Host.ServerConfiguration.DefaultEncoding = Encoding.GetEncoding(config.Server.DefaultEncoding);
prov.Host.ServerConfiguration.MaximumContentLength = config.Server.MaximumContentLength;
prov.Host.ServerConfiguration.IncludeRequestIdHeader = config.Server.IncludeRequestIdHeader;
Expand Down Expand Up @@ -128,8 +126,6 @@ internal class ConfigStructureFile__ServerConfiguration
{
public string? AccessLogsStream { get; set; } = "console";
public string? ErrorsLogsStream { get; set; }
public bool ResolveForwardedOriginAddress { get; set; } = false;
public bool ResolveForwardedOriginHost { get; set; } = false;
public string DefaultEncoding { get; set; } = "UTF-8";
public int MaximumContentLength { get; set; } = 0;
public bool IncludeRequestIdHeader { get; set; } = false;
Expand Down
1 change: 0 additions & 1 deletion src/Routing/Router.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

using Sisk.Core.Http;
using Sisk.Core.Internal;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down
1 change: 0 additions & 1 deletion src/Routing/Router__CoreSetters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Sisk.Core.Internal;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Sisk.Core.Routing;

Expand Down
4 changes: 2 additions & 2 deletions src/Sisk.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Version>1.0.0.0-rc1</Version>
<Version>1.0.0.0-rc2</Version>

<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<SignAssembly>False</SignAssembly>
Expand All @@ -51,7 +51,7 @@

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

<!-- SYSLIB0020: IgnoreNullValues is obsolete -->
<NoWarn>$(NoWarn);SYSLIB0020</NoWarn>
</PropertyGroup>
Expand Down

0 comments on commit 2fe53f0

Please sign in to comment.