Skip to content

Commit d0a0c63

Browse files
committed
Initial Setup
1 parent c3b07fe commit d0a0c63

6 files changed

Lines changed: 174 additions & 12 deletions

File tree

.github/workflows/probe.yml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,9 @@ jobs:
2020

2121
steps:
2222

23-
- name: Checkout Http11Probe
23+
- name: Checkout
2424
uses: actions/checkout@v4
2525

26-
- name: Checkout Glyph11
27-
uses: actions/checkout@v4
28-
with:
29-
repository: MDA2AV/Glyph11
30-
path: Glyph11
31-
3226
- name: Setup .NET
3327
uses: actions/setup-dotnet@v4
3428
with:
@@ -56,12 +50,9 @@ jobs:
5650
- name: Setup Rust
5751
uses: dtolnay/rust-toolchain@stable
5852

59-
- name: Build Http11Probe
53+
- name: Build
6054
run: dotnet build Http11Probe.slnx -c Release
6155

62-
- name: Build Glyph11 (GlyphServer only)
63-
run: dotnet build Glyph11/src/Glyph11.sln -c Release
64-
6556
- name: Install Flask
6657
run: pip install flask
6758

@@ -89,7 +80,7 @@ jobs:
8980

9081
- name: Start GlyphServer
9182
run: |
92-
dotnet run --no-build -c Release --project Glyph11/src/Servers/GlyphServer -- 9001 &
83+
dotnet run --no-build -c Release --project src/Servers/GlyphServer -- 9001 &
9384
echo $! > glyph.pid
9485
9586
- name: Start Kestrel (AspNetMinimal)

.idea/.idea.Http11Probe/.idea/.gitignore

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/.idea.Http11Probe/.idea/encodings.xml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Http11Probe.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</Folder>
66
<Folder Name="/src/Servers/">
77
<Project Path="src/Servers/AspNetMinimal/AspNetMinimal.csproj" />
8+
<Project Path="src/Servers/GlyphServer/GlyphServer.csproj" />
89
<Project Path="src/Servers/NancyServer/NancyServer.csproj" />
910
</Folder>
1011
</Solution>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<LangVersion>latest</LangVersion>
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
10+
<IsPackable>false</IsPackable>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Glyph11" Version="0.2.2" />
15+
</ItemGroup>
16+
17+
</Project>

src/Servers/GlyphServer/Program.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System.Buffers;
2+
using System.Net;
3+
using System.Net.Sockets;
4+
using System.Text;
5+
using Glyph11;
6+
using Glyph11.Parser.Hardened;
7+
using Glyph11.Protocol;
8+
using Glyph11.Validation;
9+
10+
var port = args.Length > 0 && int.TryParse(args[0], out var p) ? p : 5098;
11+
12+
var listener = new TcpListener(IPAddress.Loopback, port);
13+
listener.Start();
14+
15+
Console.WriteLine($"GlyphServer listening on http://localhost:{port}");
16+
17+
using var cts = new CancellationTokenSource();
18+
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
19+
20+
try
21+
{
22+
while (!cts.Token.IsCancellationRequested)
23+
{
24+
var client = await listener.AcceptTcpClientAsync(cts.Token);
25+
_ = HandleClientAsync(client, cts.Token);
26+
}
27+
}
28+
catch (OperationCanceledException) { }
29+
30+
listener.Stop();
31+
Console.WriteLine("Server stopped.");
32+
33+
static async Task HandleClientAsync(TcpClient client, CancellationToken ct)
34+
{
35+
using (client)
36+
await using (var stream = client.GetStream())
37+
{
38+
var buffer = new byte[65536];
39+
var filled = 0;
40+
var limits = ParserLimits.Default;
41+
using var request = new BinaryRequest();
42+
43+
try
44+
{
45+
while (!ct.IsCancellationRequested)
46+
{
47+
var read = await stream.ReadAsync(buffer.AsMemory(filled), ct);
48+
if (read == 0) break;
49+
filled += read;
50+
51+
while (filled > 0)
52+
{
53+
var sequence = new ReadOnlySequence<byte>(buffer, 0, filled);
54+
55+
try
56+
{
57+
if (!HardenedParser.TryExtractFullHeader(ref sequence, request, in limits, out var bytesRead))
58+
break; // Need more data
59+
60+
// Post-parse semantic validation
61+
if (RequestSemantics.HasTransferEncodingWithContentLength(request) ||
62+
RequestSemantics.HasConflictingContentLength(request) ||
63+
RequestSemantics.HasConflictingCommaSeparatedContentLength(request) ||
64+
RequestSemantics.HasInvalidContentLengthFormat(request) ||
65+
RequestSemantics.HasContentLengthWithLeadingZeros(request) ||
66+
RequestSemantics.HasInvalidHostHeaderCount(request) ||
67+
RequestSemantics.HasInvalidTransferEncoding(request) ||
68+
RequestSemantics.HasDotSegments(request) ||
69+
RequestSemantics.HasFragmentInRequestTarget(request) ||
70+
RequestSemantics.HasBackslashInPath(request) ||
71+
RequestSemantics.HasDoubleEncoding(request) ||
72+
RequestSemantics.HasEncodedNullByte(request) ||
73+
RequestSemantics.HasOverlongUtf8(request))
74+
{
75+
await stream.WriteAsync(MakeErrorResponse(400, "Bad Request"), ct);
76+
return;
77+
}
78+
79+
var method = Encoding.ASCII.GetString(request.Method.Span);
80+
var path = Encoding.ASCII.GetString(request.Path.Span);
81+
var responseBytes = BuildResponse(method, path);
82+
await stream.WriteAsync(responseBytes, ct);
83+
84+
// Consume parsed bytes and reset for keep-alive
85+
if (bytesRead > 0 && bytesRead <= filled)
86+
{
87+
Buffer.BlockCopy(buffer, bytesRead, buffer, 0, filled - bytesRead);
88+
filled -= bytesRead;
89+
}
90+
else
91+
{
92+
filled = 0;
93+
}
94+
95+
request.Clear();
96+
}
97+
catch (HttpParseException ex)
98+
{
99+
var (code, reason) = ex.IsLimitViolation
100+
? (431, "Request Header Fields Too Large")
101+
: (400, "Bad Request");
102+
await stream.WriteAsync(MakeErrorResponse(code, reason), ct);
103+
return;
104+
}
105+
}
106+
}
107+
}
108+
catch (OperationCanceledException) { }
109+
catch (IOException) { }
110+
}
111+
}
112+
113+
static byte[] BuildResponse(string method, string path)
114+
{
115+
var body = $"Hello from GlyphServer\r\nMethod: {method}\r\nPath: {path}\r\n";
116+
return MakeResponse(200, "OK", body);
117+
}
118+
119+
static byte[] MakeResponse(int status, string reason, string body)
120+
{
121+
var bodyBytes = Encoding.UTF8.GetBytes(body);
122+
var header = $"HTTP/1.1 {status} {reason}\r\nContent-Type: text/plain\r\nContent-Length: {bodyBytes.Length}\r\nConnection: keep-alive\r\n\r\n";
123+
var headerBytes = Encoding.ASCII.GetBytes(header);
124+
125+
var result = new byte[headerBytes.Length + bodyBytes.Length];
126+
Buffer.BlockCopy(headerBytes, 0, result, 0, headerBytes.Length);
127+
Buffer.BlockCopy(bodyBytes, 0, result, headerBytes.Length, bodyBytes.Length);
128+
return result;
129+
}
130+
131+
static byte[] MakeErrorResponse(int status, string reason)
132+
{
133+
return MakeResponse(status, reason, $"{status} {reason}\r\n");
134+
}

0 commit comments

Comments
 (0)