Skip to content

Instantly share code, notes, and snippets.

@benaadams
Created February 18, 2020 10:17
Show Gist options
  • Save benaadams/0a44b0cb1c0aab57d6525a0eedc0672f to your computer and use it in GitHub Desktop.
Save benaadams/0a44b0cb1c0aab57d6525a0eedc0672f to your computer and use it in GitHub Desktop.
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