Skip to content

Commit db5819d

Browse files
committed
TEMP
1 parent cb78633 commit db5819d

33 files changed

Lines changed: 134 additions & 4173 deletions

src/TurboHTTP.Tests/Http11/Http11ClientEncoderSpec.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public void Encode_should_add_host_header()
3535
}
3636

3737
[Fact(Timeout = 5000)]
38-
public void Encode_should_handle_post_with_body()
38+
public void Encode_should_write_headers_with_content_length()
3939
{
4040
var request = new HttpRequestMessage(HttpMethod.Post, "http://example.com/")
4141
{
@@ -49,7 +49,6 @@ public void Encode_should_handle_post_with_body()
4949
var result = Encoding.ASCII.GetString(buffer, 0, written);
5050
Assert.Contains("POST / HTTP/1.1", result);
5151
Assert.Contains("Content-Length: 9", result);
52-
Assert.DoesNotContain("test body", result);
5352
}
5453

5554
[Fact(Timeout = 5000)]

src/TurboHTTP.Tests/Protocol/LineBased/BodyEncoderFactorySpec.cs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,15 @@ public override void Flush() { }
2222
[Fact(Timeout = 5000)]
2323
public void Create_should_return_null_for_null_content()
2424
{
25-
var encoder = BodyEncoderFactory.Create(null, HttpVersion.Version11, 65_536);
25+
var encoder = BodyEncoderFactory.Create(null, HttpVersion.Version11);
2626
Assert.Null(encoder);
2727
}
2828

2929
[Fact(Timeout = 5000)]
30-
public void Create_should_return_buffered_for_http11_small_body()
30+
public void Create_should_return_streamed_for_http11_known_length()
3131
{
3232
var content = new ByteArrayContent(new byte[100]);
33-
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version11, 65_536);
34-
Assert.IsType<ContentLengthBufferedBodyEncoder>(encoder);
35-
encoder.Dispose();
36-
}
37-
38-
[Fact(Timeout = 5000)]
39-
public void Create_should_return_streamed_for_http11_large_body()
40-
{
41-
var content = new ByteArrayContent(new byte[200_000]);
42-
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version11, 65_536);
33+
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version11);
4334
Assert.IsType<ContentLengthStreamedBodyEncoder>(encoder);
4435
encoder.Dispose();
4536
}
@@ -51,7 +42,7 @@ public void Create_should_return_chunked_and_set_header_for_http11_unknown_lengt
5142
var content = new StreamContent(new NonSeekableStream());
5243
request.Content = content;
5344

54-
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version11, 65_536, request.Headers);
45+
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version11, request.Headers);
5546

5647
Assert.IsType<ChunkedBodyEncoder>(encoder);
5748
Assert.True(request.Headers.TransferEncodingChunked);
@@ -62,7 +53,7 @@ public void Create_should_return_chunked_and_set_header_for_http11_unknown_lengt
6253
public void Create_should_return_buffered_for_http10_known_length()
6354
{
6455
var content = new ByteArrayContent(new byte[200_000]);
65-
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version10, 65_536);
56+
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version10);
6657
Assert.IsType<ContentLengthBufferedBodyEncoder>(encoder);
6758
encoder.Dispose();
6859
}
@@ -71,7 +62,7 @@ public void Create_should_return_buffered_for_http10_known_length()
7162
public void Create_should_return_buffered_for_http10_unknown_length()
7263
{
7364
var content = new StreamContent(new MemoryStream(new byte[100]));
74-
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version10, 65_536);
65+
var encoder = BodyEncoderFactory.Create(content, HttpVersion.Version10);
7566
Assert.IsType<ContentLengthBufferedBodyEncoder>(encoder);
7667
encoder.Dispose();
7768
}

src/TurboHTTP.Tests/Protocol/Syntax/Http10/HeaderRouterSpec.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void Apply_should_route_content_headers_to_content()
2222
}
2323

2424
[Fact(Timeout = 5000)]
25-
public void Apply_should_strip_HopByHop_headers()
25+
public void Apply_should_include_hop_by_hop_headers()
2626
{
2727
var parsed = new HeaderCollection();
2828
parsed.Add("Connection", "close");
@@ -32,8 +32,8 @@ public void Apply_should_strip_HopByHop_headers()
3232
var msg = new HttpResponseMessage { Content = new ByteArrayContent([]) };
3333
HeaderRouter.ApplyToResponse(msg, parsed);
3434

35-
Assert.False(msg.Headers.Contains("Connection"));
36-
Assert.False(msg.Headers.Contains("Keep-Alive"));
35+
Assert.True(msg.Headers.Contains("Connection"));
36+
Assert.True(msg.Headers.Contains("Keep-Alive"));
3737
Assert.True(msg.Headers.Contains("X-Custom"));
3838
}
3939

src/TurboHTTP.Tests/Security/Http11FuzzBodySpec.cs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,52 @@
11
using System.Text;
2-
using Decoder = TurboHTTP.Protocol.Http11.Decoder;
2+
using TurboHTTP.Protocol.Syntax;
3+
using TurboHTTP.Protocol.Syntax.Http11.Client;
4+
using TurboHTTP.Protocol.Syntax.Http11.Options;
35

46
namespace TurboHTTP.Tests.Security;
57

68
public sealed class Http11FuzzBodySpec
79
{
810
private const int IterationsPerSeed = 100;
911
private const long MaxBytesPerIteration = 1_048_576;
12+
private static readonly Http11ClientDecoderOptions DecoderOptions = Http11ClientDecoderOptions.Default;
1013

11-
private static void AssertDecodeNeverCrashes(Decoder decoder, ReadOnlyMemory<byte> data)
14+
private static void AssertDecodeNeverCrashes(Http11ClientDecoder decoder, ReadOnlyMemory<byte> data)
1215
{
1316
try
1417
{
15-
decoder.TryDecodeHeaders(data, out var response, out _, out _);
16-
response?.Dispose();
18+
var outcome = decoder.Feed(data.Span, requestMethodWasHead: false, out _);
19+
if (outcome == DecodeOutcome.Complete)
20+
{
21+
var response = decoder.GetResponse();
22+
response.Dispose();
23+
decoder.Reset();
24+
}
1725
}
1826
catch (HttpProtocolException)
1927
{
20-
// Expected — malformed input correctly classified by our decoder.
2128
}
2229
catch (FormatException)
2330
{
24-
// Expected — .NET's HttpResponseMessage rejects invalid reason phrases
25-
// (newlines, NUL) that random bytes produce. Not a decoder bug.
2631
}
2732
}
2833

29-
private static void AssertDecodeEofNeverCrashes(Decoder decoder)
34+
private static void AssertDecodeEofNeverCrashes(Http11ClientDecoder decoder)
3035
{
3136
try
3237
{
33-
decoder.TryDecodeEof(out var response);
34-
response?.Dispose();
38+
if (decoder.SignalEof() || decoder.IsBodyComplete)
39+
{
40+
var response = decoder.GetResponse();
41+
response?.Dispose();
42+
decoder.Reset();
43+
}
3544
}
3645
catch (HttpProtocolException)
3746
{
38-
// Expected — malformed input correctly classified by our decoder.
3947
}
4048
catch (FormatException)
4149
{
42-
// Expected — .NET's HttpResponseMessage rejects invalid reason phrases.
4350
}
4451
}
4552

@@ -102,7 +109,7 @@ public void Http11Decoder_should_handle_mixed_transfer_encoding_and_content_leng
102109
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
103110
var allocBefore = GC.GetAllocatedBytesForCurrentThread();
104111

105-
using var decoder = new Decoder();
112+
var decoder = new Http11ClientDecoder(DecoderOptions);
106113

107114
var sb = new StringBuilder();
108115
sb.Append("HTTP/1.1 200 OK\r\n");
@@ -151,7 +158,7 @@ public void Http11Decoder_should_handle_extremely_large_content_length_without_o
151158
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
152159
var allocBefore = GC.GetAllocatedBytesForCurrentThread();
153160

154-
using var decoder = new Decoder();
161+
var decoder = new Http11ClientDecoder(DecoderOptions);
155162

156163
var claimedLengths = new[]
157164
{
@@ -207,7 +214,7 @@ public void Http11Decoder_should_handle_connection_close_with_trailing_data(int
207214
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
208215
var allocBefore = GC.GetAllocatedBytesForCurrentThread();
209216

210-
using var decoder = new Decoder();
217+
var decoder = new Http11ClientDecoder(DecoderOptions);
211218

212219
var body = "Hello, World!";
213220
var validResponse = BuildValidResponse(200, "OK", body,
@@ -250,7 +257,7 @@ public void Http11Decoder_should_maintain_consistent_state_with_fragmented_deliv
250257
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
251258
var allocBefore = GC.GetAllocatedBytesForCurrentThread();
252259

253-
using var decoder = new Decoder();
260+
var decoder = new Http11ClientDecoder(DecoderOptions);
254261

255262
byte[] fullResponse;
256263
if (rng.Next(2) == 0)

src/TurboHTTP.Tests/Security/UriRedirectSpec.cs

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
using System.Net;
22
using System.Text;
3+
using Akka.Actor;
34
using TurboHTTP.Protocol.Semantics;
4-
using Encoder = TurboHTTP.Protocol.Http11.Encoder;
5+
using TurboHTTP.Protocol.Syntax.Http11.Client;
6+
using TurboHTTP.Protocol.Syntax.Http11.Options;
57

68
namespace TurboHTTP.Tests.Security;
79

810
public sealed class UriRedirectSpec
911
{
10-
private static string EncodeHttp11(HttpRequestMessage request, bool absoluteForm = false, int bufferSize = 16384)
12+
private static readonly Http11ClientEncoder Encoder = new(Http11ClientEncoderOptions.Default);
13+
14+
private static string EncodeHttp11(HttpRequestMessage request, int bufferSize = 16384)
1115
{
12-
var buffer = new Memory<byte>(new byte[bufferSize]);
13-
var span = buffer.Span;
14-
var written = Encoder.Encode(request, ref span, absoluteForm);
15-
return Encoding.ASCII.GetString(buffer.Span[..written]);
16+
var buffer = new byte[bufferSize];
17+
var written = Encoder.Encode(buffer, request, ActorRefs.Nobody);
18+
return Encoding.ASCII.GetString(buffer, 0, written);
1619
}
1720

1821
private static HttpResponseMessage RedirectResponse(HttpStatusCode status, string location)
@@ -25,35 +28,25 @@ private static HttpResponseMessage RedirectResponse(HttpStatusCode status, strin
2528
[Fact(Timeout = 5000)]
2629
public void Uri_should_normalize_backslash_when_path_contains_backslash()
2730
{
28-
// Attack: Backslash path traversal on Windows (e.g., ..\..\etc\passwd)
29-
// .NET Uri normalizes backslashes to forward slashes on Windows
3031
const string uriString = "https://example.com/api\\..\\sensitive";
3132

32-
// On Windows, Uri may normalize backslashes — test the actual behavior
3333
if (OperatingSystem.IsWindows())
3434
{
3535
var uri = new Uri(uriString);
36-
// Backslash should be converted to forward slash
3736
Assert.DoesNotContain("\\", uri.AbsolutePath);
3837
}
3938
}
4039

4140
[Fact(Timeout = 5000)]
4241
public void Http11Encoder_should_encode_extremely_long_uri_when_uri_exceeds_standard_size()
4342
{
44-
// Attack: Resource exhaustion via extremely long URIs
45-
var longPath = string.Concat(Enumerable.Repeat("segment/", 400)); // ~3200 chars
43+
var longPath = string.Concat(Enumerable.Repeat("segment/", 400));
4644
var longUri = $"https://example.com/{longPath}query=value";
4745

4846
var request = new HttpRequestMessage(HttpMethod.Get, longUri);
4947

50-
// Use a larger buffer to accommodate the long URI
51-
const int bufferSize = 32768; // 32 KB
52-
var buffer = new Memory<byte>(new byte[bufferSize]);
53-
var span = buffer.Span;
54-
55-
// Should encode without throwing
56-
var written = Encoder.Encode(request, ref span);
48+
const int bufferSize = 32768;
49+
var written = Encoder.Encode(new byte[bufferSize], request, ActorRefs.Nobody);
5750

5851
Assert.True(written > 0);
5952
Assert.True(written < bufferSize);
@@ -62,17 +55,13 @@ public void Http11Encoder_should_encode_extremely_long_uri_when_uri_exceeds_stan
6255
[Fact(Timeout = 5000)]
6356
public void Http11Encoder_should_encode_long_query_string_when_query_parameters_very_large()
6457
{
65-
// Attack: Query string DoS via extremely long parameter values
6658
var longQueryValue = string.Concat(Enumerable.Repeat("x", 4096));
6759
var uri = $"https://example.com/endpoint?data={longQueryValue}";
6860

6961
var request = new HttpRequestMessage(HttpMethod.Get, uri);
7062

7163
const int bufferSize = 32768;
72-
var buffer = new Memory<byte>(new byte[bufferSize]);
73-
var span = buffer.Span;
74-
75-
var written = Encoder.Encode(request, ref span);
64+
var written = Encoder.Encode(new byte[bufferSize], request, ActorRefs.Nobody);
7665

7766
Assert.True(written > 0);
7867
Assert.True(written < bufferSize);
@@ -81,19 +70,15 @@ public void Http11Encoder_should_encode_long_query_string_when_query_parameters_
8170
[Fact(Timeout = 5000)]
8271
public void RedirectHandler_should_strip_userinfo_in_location_when_location_contains_credentials()
8372
{
84-
// Attack: Location header with embedded credentials
85-
// https://user:password@evil.com/phishing
8673
var original = new HttpRequestMessage(HttpMethod.Get, "https://trusted.com/page");
8774
var response = RedirectResponse(HttpStatusCode.Found, "https://admin:secret@attacker.com/");
8875

8976
var handler = new RedirectHandler();
9077
var redirect = handler.BuildRedirectRequest(original, response);
9178

92-
// The redirect URI will contain userinfo in the Uri object, but encoders strip it
9379
Assert.NotNull(redirect.RequestUri);
9480

95-
// If we encode the redirect request, userinfo should not appear
96-
var encoded = EncodeHttp11(redirect, absoluteForm: true);
81+
var encoded = EncodeHttp11(redirect);
9782
Assert.DoesNotContain("admin", encoded);
9883
Assert.DoesNotContain("secret", encoded);
9984
Assert.DoesNotContain("@", encoded);

0 commit comments

Comments
 (0)