Uwaa/HTTP/HttpRequest.cs

169 lines
5.2 KiB
C#
Raw Permalink Normal View History

2024-11-22 07:40:43 +01:00
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Collections.Specialized;
using Uwaa.HTTP.Websockets;
namespace Uwaa.HTTP;
/// <summary>
/// Contains the method, path, query, headers, and content of a HTTP request.
/// </summary>
2024-11-22 09:08:27 +01:00
public record HttpRequest
2024-11-22 07:40:43 +01:00
{
/// <summary>
/// The HTTP method of the request.
/// </summary>
2024-11-22 09:08:27 +01:00
public HttpMethod Method { get; set; } = HttpMethod.Any;
2024-11-22 07:40:43 +01:00
/// <summary>
/// The HTTP path being requested.
/// </summary>
2024-11-22 09:08:27 +01:00
public string Path { get; set; } = string.Empty;
2024-11-22 07:40:43 +01:00
/// <summary>
/// Additional paramters in the path of the request.
/// </summary>
2024-11-22 09:08:27 +01:00
public NameValueCollection Query { get; set; }
2024-11-22 07:40:43 +01:00
/// <summary>
/// HTTP header fields included in the request.
/// </summary>
2024-11-22 09:08:27 +01:00
public HttpFields Fields { get; set; }
2024-11-22 07:40:43 +01:00
/// <summary>
/// The body of the HTTP request, if any.
/// </summary>
2024-11-22 09:08:27 +01:00
public HttpContent? Content { get; set; }
2024-11-22 07:40:43 +01:00
/// <summary>
/// If true, the connection is requesting to be upgrading to a websocket.
/// </summary>
public bool IsWebsocket => Fields.Upgrade == "websocket";
/// <summary>
/// Whether or not the request is valid.
/// </summary>
public bool Valid => Method != HttpMethod.Any && Path.StartsWith('/');
public HttpRequest(HttpMethod method, string path, HttpContent? content = null) : this(method, path, new HttpFields(), content)
{
}
public HttpRequest(HttpMethod method, string path, HttpFields fields, HttpContent? content = null)
{
Method = method;
Fields = fields;
Content = content;
string[] pathParts = path.Split('?', 2, StringSplitOptions.RemoveEmptyEntries);
Path = pathParts[0];
Query = HttpUtility.ParseQueryString(pathParts.Length > 1 ? pathParts[1] : string.Empty);
}
/// <summary>
/// Returns true if the client is accepting the provided type, otherwise false.
/// </summary>
public bool CanAccept(MIMEType type)
{
if (Fields.Accept == null)
return true;
for (int i = 0; i < Fields.Accept.Length; i++)
if (Fields.Accept[i].Match(type))
return true;
return false;
}
internal async Task WriteTo(HttpStream stream)
{
StringBuilder sb = new StringBuilder();
sb.Append(Method.ToString());
sb.Append(' ');
sb.Append(Path);
if (Query != null && Query.Count > 0)
{
sb.Append('?');
for (int i = 0; i < Query.Count; i++)
{
if (i > 0)
sb.Append('&');
sb.Append(HttpUtility.UrlEncode(Query.GetKey(i)));
sb.Append('=');
sb.Append(HttpUtility.UrlEncode(Query[i]));
}
}
sb.Append(" HTTP/1.1\r\n");
void writeField(string key, string value)
{
sb.Append(key);
sb.Append(": ");
sb.Append(value);
sb.Append("\r\n");
}
if (Content.HasValue)
{
Fields.ContentLength = Content.Value.Content.Length;
Fields.ContentType = Content.Value.Type.ToString();
}
else
{
Fields.ContentLength = null;
Fields.ContentType = null;
}
Fields.EmitAll(writeField);
sb.Append("\r\n");
await stream.Write(Encoding.ASCII.GetBytes(sb.ToString()));
if (Content.HasValue)
await stream.Write(Content.Value.Content);
await stream.Flush();
}
/// <summary>
/// Generates a response which upgrades the connection to a websocket.
/// </summary>
/// <param name="callback">The websocket execution function to call.</param>
/// <param name="protocols">Subprotocols which can be accepted. If null or empty, any protocol will be accepted.</param>
/// <returns>Returns a <seealso cref="SwitchingProtocols"/> response.</returns>
/// <remarks>
/// If an upgrade has not been requested or no subprotocol can be negotiated, this will return null.
/// </remarks>
public SwitchingProtocols? UpgradeToWebsocket(WebsocketHandler callback, params string[]? protocols)
{
if (Fields.WebSocketKey == null)
return null;
//Subprotocol negotiation
string? chosenProtocol = null;
string? requestedProtocols = Fields.WebSocketProtocol;
if (requestedProtocols != null && protocols != null && protocols.Length > 0)
{
foreach (string requested in requestedProtocols.ToLower().Split(',', StringSplitOptions.TrimEntries))
{
foreach (string supported in protocols)
{
if (requested.Equals(supported, StringComparison.InvariantCultureIgnoreCase))
{
chosenProtocol = supported;
goto a;
}
}
}
return null;
}
a:
string acceptKey = Convert.ToBase64String(SHA1.HashData(Encoding.ASCII.GetBytes(Fields.WebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")));
return new SwitchingProtocols(acceptKey, chosenProtocol, callback);
}
}