http: example fixes and security fixes

This commit is contained in:
uwaa 2024-12-25 22:14:53 +00:00
parent 97e7d5db5d
commit b4052c6460
8 changed files with 46 additions and 20 deletions

View file

@ -51,8 +51,8 @@ static class Program
router.Add(new CORS());
router.Add("custom", new CustomRoute());
router.Add("subpath", subpath);
router.Add(new FileEndpoint("www-static"));
router.Add(new FileEndpoint("www-dynamic"));
router.Add(new FileEndpoint("www-static") { Index = false });
router.Add(new FileEndpoint("www-dynamic") { Index = false });
router.Add("", Root);
router.Add(new StaticEndpoint(HttpResponse.NotFound("File not found")));
return router;
@ -109,7 +109,7 @@ static class Program
if (req.IsWebsocket)
return Websocket(req, info);
byte[] indexFile = await File.ReadAllBytesAsync("www-static/index.htm");
byte[] indexFile = await File.ReadAllBytesAsync("www-static/index.html");
HttpContent html = new HttpContent(new MIMEType("text", "html"), indexFile);
return HttpResponse.OK(html);
}

View file

@ -21,7 +21,7 @@
<div style="padding-left:20px;">
<a href="./websocket">Websocket test</a><br>
<a href="./custom/foo/bar">Custom route</a><br>
<a href="./test">test.htm</a><br>
<a href="./test">test.html</a><br>
<br>
<a href="./subpath/foo">Foo</a><br>
<a href="./subpath/bar">Bar</a><br>

View file

@ -42,7 +42,7 @@ public sealed class HttpClient
innerStream = ssl;
}
HttpStream stream = new HttpStream(innerStream);
HttpStream stream = new HttpStream(innerStream, TimeSpan.FromSeconds(60));
//Send request
await FixRequest(request, host, ConnectionType.Close).WriteTo(stream);
@ -136,7 +136,7 @@ public sealed class HttpClient
innerStream = ssl;
}
stream = new HttpStream(innerStream);
stream = new HttpStream(innerStream, Timeout);
}
try

View file

@ -56,7 +56,7 @@ public sealed class HttpServer
public event Action<IPEndPoint, Exception>? OnException;
/// <summary>
/// The maximum time the socket may be inactive before it is presumed dead and closed.
/// The maximum time a socket may be inactive before it is presumed dead and closed.
/// </summary>
public TimeSpan Timeout = TimeSpan.FromSeconds(20);
@ -139,11 +139,11 @@ public sealed class HttpServer
//HTTP request-response loop
while (client.Connected)
{
HttpStream httpStream = new HttpStream(stream);
HttpStream httpStream = new HttpStream(stream, Timeout);
try
{
HttpClientInfo clientInfo = new HttpClientInfo(client, endpoint);
HttpRequest req = await httpStream.ReadRequest().WaitAsync(Timeout);
HttpRequest req = await httpStream.ReadRequest();
//Parse path
ArraySegment<string> pathSpl = req.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
@ -161,14 +161,14 @@ public sealed class HttpServer
HttpResponse? response = (await Router.GetResponse(req, clientInfo, pathSpl)) ?? HttpResponse.NotFound("Router produced no response");
OnResponse?.Invoke(req, clientInfo, response);
await response.WriteTo(httpStream).WaitAsync(Timeout);
await response.WriteTo(httpStream);
if (response is SwitchingProtocols swp)
{
//Create and run websocket
WebsocketRemote ws = new WebsocketRemote(req, clientInfo, httpStream, swp.Fields.WebSocketProtocol);
CloseStatus closeStatus = await swp.Callback(ws);
await ws.Close(closeStatus).WaitAsync(Timeout);
await ws.Close(closeStatus);
break; //Close
}

View file

@ -23,15 +23,25 @@ class HttpStream : IDisposable
/// </summary>
readonly Decoder Decoder;
public HttpStream(Stream stream) : base()
/// <summary>
/// The maximum time the socket may be inactive before it is presumed dead and closed.
/// </summary>
public TimeSpan Timeout;
public HttpStream(Stream stream, TimeSpan timeout) : base()
{
Stream = stream;
Timeout = timeout;
Buffer = new BufferedStream(stream);
Decoder = Encoding.ASCII.GetDecoder();
}
public async ValueTask<string> ReadLine()
{
CancellationTokenSource cancelSrc = new CancellationTokenSource();
cancelSrc.CancelAfter(Timeout);
const int maxChars = 4096;
byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(1);
char[] charBuffer = ArrayPool<char>.Shared.Rent(maxChars);
@ -40,7 +50,7 @@ class HttpStream : IDisposable
int charBufferIndex = 0;
while (true)
{
if (await Buffer.ReadAsync(dataBuffer.AsMemory(0, 1)) == 0)
if (await Buffer.ReadAsync(dataBuffer.AsMemory(0, 1), cancelSrc.Token) == 0)
if (charBufferIndex == 0)
throw new SocketException((int)SocketError.ConnectionReset);
else
@ -77,12 +87,15 @@ class HttpStream : IDisposable
public async ValueTask<int> Read(Memory<byte> buffer)
{
CancellationTokenSource cancelSrc = new CancellationTokenSource();
cancelSrc.CancelAfter(Timeout);
try
{
int index = 0;
while (index < buffer.Length)
{
int count = await Buffer.ReadAsync(buffer[index..]);
int count = await Buffer.ReadAsync(buffer[index..], cancelSrc.Token);
if (count == 0)
break;
@ -102,8 +115,11 @@ class HttpStream : IDisposable
public ValueTask Write(string text)
{
CancellationTokenSource cancelSrc = new CancellationTokenSource();
cancelSrc.CancelAfter(Timeout);
byte[] data = Encoding.ASCII.GetBytes(text);
return Buffer.WriteAsync(data);
return Buffer.WriteAsync(data, cancelSrc.Token);
}
public ValueTask WriteLine(string text)
@ -118,9 +134,11 @@ class HttpStream : IDisposable
public ValueTask Write(ReadOnlyMemory<byte> bytes)
{
CancellationTokenSource cancelSrc = new CancellationTokenSource();
cancelSrc.CancelAfter(Timeout);
try
{
return Buffer.WriteAsync(bytes);
return Buffer.WriteAsync(bytes, cancelSrc.Token);
}
catch (IOException e)
{
@ -133,10 +151,12 @@ class HttpStream : IDisposable
public async Task Flush()
{
CancellationTokenSource cancelSrc = new CancellationTokenSource();
cancelSrc.CancelAfter(Timeout);
try
{
await Buffer.FlushAsync();
await Stream.FlushAsync();
await Buffer.FlushAsync(cancelSrc.Token);
await Stream.FlushAsync(cancelSrc.Token);
}
catch (IOException e)
{

View file

@ -58,6 +58,11 @@ public partial class FileEndpoint : RouterBase
/// </summary>
public string Directory;
/// <summary>
/// If true, an empty path will be interpreted as "index.html".
/// </summary>
public bool Index = true;
public override HttpMethod Method => HttpMethod.GET;
public override int Arguments => 1;
@ -73,12 +78,13 @@ public partial class FileEndpoint : RouterBase
if (FilenameChecker().IsMatch(asset))
return HttpResponse.BadRequest("Illegal chars in asset path");
return await GetFile(asset);
HttpContent? file = await GetFile(asset);
return file == null ? null : HttpResponse.OK(file);
}
protected async ValueTask<HttpContent?> GetFile(string asset)
{
if (asset == "")
if (Index && asset == "")
asset = "index.html";
string assetPath = $"{Directory}/{asset}";