-
-
Save benaadams/0a44b0cb1c0aab57d6525a0eedc0672f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Buffers; | |
using System.Runtime.CompilerServices; | |
using System.IO; | |
public class Http1Connection : IHttpRequestLineHandler, IHttpHeadersHandler | |
{ | |
public static void Main(string[] args) | |
{ | |
Console.WriteLine(); | |
} | |
private long _remainingRequestHeadersBytesAllowed = 4096; | |
private ITimeoutControl TimeoutControl { get; } = new TimeoutControl(); | |
private IHttpParser<Http1ParsingHandler> _parser; | |
public bool TakeMessageHeaders(in ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined) | |
{ | |
// Make sure the buffer is limited | |
if (buffer.Length > _remainingRequestHeadersBytesAllowed) | |
{ | |
// Input oversize, cap amount checked | |
return TrimAndTakeMessageHeaders(buffer, trailers, out consumed, out examined); | |
} | |
var reader = new SequenceReader<byte>(buffer); | |
var result = false; | |
try | |
{ | |
result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader); | |
if (result) | |
{ | |
TimeoutControl.CancelTimeout(); | |
} | |
return result; | |
} | |
finally | |
{ | |
consumed = reader.Position; | |
_remainingRequestHeadersBytesAllowed -= (int)reader.Consumed; | |
if (result) | |
{ | |
examined = consumed; | |
} | |
else | |
{ | |
examined = buffer.End; | |
} | |
} | |
bool TrimAndTakeMessageHeaders(in ReadOnlySequence<byte> buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined) | |
{ | |
var trimmedBuffer = buffer.Slice(buffer.Start, _remainingRequestHeadersBytesAllowed); | |
var reader = new SequenceReader<byte>(trimmedBuffer); | |
var result = false; | |
try | |
{ | |
result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), ref reader); | |
if (!result) | |
{ | |
// We read the maximum allowed but didn't complete the headers. | |
BadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize); | |
} | |
TimeoutControl.CancelTimeout(); | |
return result; | |
} | |
finally | |
{ | |
consumed = reader.Position; | |
_remainingRequestHeadersBytesAllowed -= (int)reader.Consumed; | |
if (result) | |
{ | |
examined = consumed; | |
} | |
else | |
{ | |
examined = trimmedBuffer.End; | |
} | |
} | |
} | |
} | |
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) | |
{ | |
throw new NotImplementedException(); | |
} | |
public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) | |
{ | |
throw new NotImplementedException(); | |
} | |
public void OnHeadersComplete(bool endStream) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
public interface IHttpHeadersHandler | |
{ | |
void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value); | |
void OnHeadersComplete(bool endStream); | |
} | |
internal readonly struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler | |
{ | |
public readonly Http1Connection Connection; | |
public readonly bool Trailers; | |
public Http1ParsingHandler(Http1Connection connection) | |
{ | |
Connection = connection; | |
Trailers = false; | |
} | |
public Http1ParsingHandler(Http1Connection connection, bool trailers) | |
{ | |
Connection = connection; | |
Trailers = trailers; | |
} | |
public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) | |
{ | |
Connection.OnHeader(name, value); | |
} | |
public void OnHeadersComplete(bool endStream) | |
{ | |
Connection.OnHeadersComplete(endStream); | |
} | |
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded) | |
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); | |
} | |
public sealed class BadHttpRequestException : IOException | |
{ | |
private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason) | |
: this(message, statusCode, reason, null) | |
{ } | |
private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason, HttpMethod? requiredMethod) | |
: base(message) | |
{ | |
StatusCode = statusCode; | |
Reason = reason; | |
} | |
public int StatusCode { get; } | |
internal RequestRejectionReason Reason { get; } | |
internal static void Throw(RequestRejectionReason reason) | |
{ | |
throw GetException(reason); | |
} | |
internal static void Throw(RequestRejectionReason reason, HttpMethod method) | |
=> throw GetException(reason, method.ToString().ToUpperInvariant()); | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
internal static BadHttpRequestException GetException(RequestRejectionReason reason) | |
{ | |
BadHttpRequestException ex = new BadHttpRequestException("BadRequest_InvalidRequestHeadersNoCRLF", 400, reason); | |
return ex; | |
} | |
internal static void Throw(RequestRejectionReason reason, string detail) | |
{ | |
throw GetException(reason, detail); | |
} | |
[MethodImpl(MethodImplOptions.NoInlining)] | |
internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail) | |
{ | |
BadHttpRequestException ex = new BadHttpRequestException("HttpParserTlsOverHttpError", 400, reason); | |
return ex; | |
} | |
} | |
internal interface IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler | |
{ | |
bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined); | |
bool ParseHeaders(TRequestHandler handler, ref SequenceReader<byte> reader); | |
} | |
public interface IHttpRequestLineHandler | |
{ | |
void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded); | |
} | |
internal interface ITimeoutControl | |
{ | |
void CancelTimeout(); | |
} | |
internal class TimeoutControl : ITimeoutControl | |
{ | |
public void CancelTimeout() | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
public enum RequestRejectionReason | |
{ | |
HeadersExceedMaxTotalSize | |
} | |
public enum HttpMethod : byte | |
{ | |
Get, | |
Put, | |
Delete, | |
Post, | |
Head, | |
Trace, | |
Patch, | |
Connect, | |
Options, | |
Custom, | |
None = byte.MaxValue, | |
} | |
public enum HttpVersion | |
{ | |
Unknown = -1, | |
Http10 = 0, | |
Http11 = 1, | |
Http2 = 2, | |
Http3 = 3 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment