HttpTesting/Program.cs
Siomek101 13f35270f4 yes
2025-06-14 22:57:19 +02:00

450 lines
16 KiB
C#

using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Z.Expressions;
namespace myownhttp
{
internal class Program
{
public enum HTTPMeth
{
undefined = -1,
GET = 0,
POST = 1 // add more in future
}
public class HTTPReq
{
public bool valid = false;
public string Path = "";
public string HTTPVersion = "";
public string Content = "";
public HTTPMeth Method = HTTPMeth.undefined;
public HTTPParams? Params;
public Dictionary<string,string> Headers = new Dictionary<string,string>();
public HTTPReq(string request)
{
try
{
var split = request.ReplaceLineEndings().Split(Environment.NewLine);
var splitfirst = split[0].Split(" ");
if (splitfirst[0] == "GET") Method = HTTPMeth.GET;
else if (splitfirst[0] == "POST") Method = HTTPMeth.POST;
Path = splitfirst[1]; // check if the path
HTTPVersion = splitfirst[2];
// headers
for (int i = 1; i < split.Length; i++)
{
var e = split[i];
if(e == "")
{
if(i+1 < split.Length)
{
var arraycontent = split.Skip(i+1).ToArray();
for (int j = 0; j < arraycontent.Length; j++)
{
Content += arraycontent[j] + (j == arraycontent.Length-1 ? "" : "\r\n");
}
}
try
{
Params = new(Content);
} catch(Exception eh)
{
Console.WriteLine("Can't do params :sob: -> " + eh.Message);
}
break;
}
var esplit = e.Split(": ");
Headers[esplit[0]] = esplit[1];
}
valid = true;
}
catch (Exception e)
{
valid = false;
Console.WriteLine(e.Message);
}
}
public override string ToString()
{
return $"HTTP REQUEST -> {(valid ? "Valid" : "Invalid")}, {(Method switch { HTTPMeth.undefined => "Unset/Undefined", HTTPMeth.GET => "GET", HTTPMeth.POST => "POST", _ => "MError"})} {Path} {HTTPVersion}";
}
}
public class HTTPRes
{
byte[] contentBytes = [];
public byte[] GetBytes() { return contentBytes; }
public HTTPRes SetBytes(byte[] bytes) { contentBytes = bytes; return this; }
public HTTPRes SetBytes(string str) { contentBytes = Encoding.UTF8.GetBytes(str); return this; }
public string ContentType = GetMimeType("txt");
string Status = GetStatusFromNum(200);
public string GetStatus() { return Status; }
public HTTPRes SetStatus(int num) { SetStatus(GetStatusFromNum(num)); return this; }
public HTTPRes SetStatus(string status) { Status = status; return this; }
public byte[] Build()
{
return Encoding.UTF8.GetBytes(
$"HTTP/1.0 {Status}\r\n" +
$"Content-Type: {ContentType}\n" +
$"Content-Length: {contentBytes.Length}" +
$"\n" +
$"\n"
).Concat(contentBytes).ToArray();
}
}
public class HTTPParams
{
public Dictionary<string, string> Params = new();
public HTTPParams(string param)
{
foreach (var item in param.Split("&")) {
var i = item.Split("=", 2);
Params[i[0].Replace("+", " ")] = i[1].Replace("+", " ");
}
}
public string Get(string Key,string Default)
{
return Params.GetValueOrDefault(Key, Default);
}
}
public abstract class HTTPHandler
{
public HTTPHandler() { }
public virtual void Handle(HTTPReq request, HTTPRes response, Action next)
{
// this is just a base, do nothing
next();
}
}
public class HTTPGetHandler : HTTPHandler
{
string Path = "";
Action<HTTPReq, HTTPRes, Action> Action = (HTTPReq req, HTTPRes res, Action next) => { };
public HTTPGetHandler(string path, Action<HTTPReq, HTTPRes, Action> action) { Path = path; Action = action; }
public override void Handle(HTTPReq request, HTTPRes response, Action next)
{
if (request.Method == HTTPMeth.GET && request.Path == Path) Action(request, response, next);
else next();
}
}
public class HTTPPostHandler : HTTPHandler
{
string Path = "";
Action<HTTPReq, HTTPRes, Action> Action = (HTTPReq req, HTTPRes res, Action next) => { };
public HTTPPostHandler(string path, Action<HTTPReq, HTTPRes, Action> action) { Path = path; Action = action; }
public override void Handle(HTTPReq request, HTTPRes response, Action next)
{
if (request.Method == HTTPMeth.POST && request.Path == Path) Action(request, response, next);
else next();
}
}
public class HTTPServeFileHandler : HTTPHandler
{
HTTPGetHandler replacement;
public HTTPServeFileHandler(string path, string filepath, string MimeType)
{
replacement = new(path, (req, res, next) =>
{
if (File.Exists(filepath))
{
res.SetBytes(File.ReadAllBytes(filepath)).SetStatus(200).ContentType = MimeType;
}
else next();
});
}
public override void Handle(HTTPReq request, HTTPRes response, Action next)
{
replacement.Handle(request, response, next);
}
}
public class HTTPStaticFilesHandler : HTTPHandler
{
string Files = "";
public HTTPStaticFilesHandler(string pathWithStatic) { Files = pathWithStatic; }
public override void Handle(HTTPReq request, HTTPRes response, Action next)
{
var pat = request.Path.Replace("..", "");
if (pat.EndsWith("/")) pat += "index.html";
if (File.Exists(Files + Path.DirectorySeparatorChar + pat))
{
var ex = Path.GetExtension(Files + Path.DirectorySeparatorChar + pat);
if (ex == string.Empty) ex = ".txt";
ex = ex.Substring(1);
response.SetBytes(
ex == "shtml" ?
Encoding.UTF8.GetBytes(ModifySHTML(request,File.ReadAllText(Files + Path.DirectorySeparatorChar + pat))) :
File.ReadAllBytes(Files + Path.DirectorySeparatorChar + pat)
).SetStatus(200).ContentType = GetMimeType(ex);
}
else next();
}
}
public class HTTPConditionHandler : HTTPHandler
{
HTTPHandler Handler = null;
Func<HTTPReq, HTTPRes, bool> Condition = (HTTPReq request, HTTPRes response) => { return false; };
public HTTPConditionHandler(Func<HTTPReq, HTTPRes, bool> cond, HTTPHandler handler) { Handler = handler; }
public override void Handle(HTTPReq request, HTTPRes response, Action next)
{
if(Handler == null || !Condition(request,response))
{
next();
return;
}
Handler.Handle(request, response, next);
}
}
public class HTTPServer
{
TcpListener listener;
public HTTPServer(int port)
{
listener = new(IPAddress.Any, 8003);
}
public List<HTTPHandler> Handlers = new();
public void Listen()
{
listener.Start();
while (true)
{
var a = listener.AcceptTcpClient();
var t = new Task(() =>
{
try
{
var s = a.GetStream();
byte[] bytes = new byte[2048];
string data = null;
int i;
// Loop to receive all the data sent by the client.
while ((i = s.Read(bytes, 0, bytes.Length)) != 0)
{
// Translate data bytes to a ASCII string.
data = Encoding.ASCII.GetString(bytes, 0, i);
var req = new HTTPReq(data);
Console.WriteLine(req.ToString());
if (req.valid)
{
HTTPRes res = new HTTPRes();
int nextAct = 0;
Action next = () => { };
next = () =>
{
if (nextAct >= Handlers.Count)
{
res.SetBytes("404 Not found.").SetStatus(404).ContentType = GetMimeType("txt");
}
else
{
Handlers[nextAct].Handle(req, res, () =>
{
nextAct++;
next();
});
}
};
next();
s.Write(res.Build());
a.Close();
return;
}
else
{
// Send back a response.
var nf = "uuh";
var nfb = Encoding.UTF8.GetBytes(nf);
s.Write(
Encoding.UTF8.GetBytes(
$"HTTP/1.0 404 Not Found\n" +
$"Content-Type: text/plain\n" +
$"Content-Length: {nfb.Length}" +
$"\n" +
$"\n" +
$"{nf}"
)
);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
});
t.Start();
}
}
}
public static string ModifySHTML(HTTPReq req, string code)
{
var modified = "";
for (var i = 0; i < code.Length; i++)
{
if (!(i + 7 > code.Length) && code[i] == '<' && code[i+1] == ':' && code[i+2] == '>')
{
i += 3;
var subst1 = code.Substring(i);
var find = subst1.IndexOf("</:>");
var scode = subst1.Substring(0, find);
try
{
var resultdn = Eval.Execute<dynamic>(scode, new { Request = req });
var result = resultdn.ToString();
modified += result;
} catch(Exception e)
{
modified += "[Error occured while executing SharpHTML].";
Console.WriteLine(e);
}
// TODO: eval
i += find+3;
} else
{
modified += code[i];
}
}
return modified;
}
static void Main(string[] args)
{
Console.WriteLine("hello");
HTTPServer http = new(8003);
http.Handlers.Add(new HTTPGetHandler("/servertime", (req, res, next) =>
{
res.SetStatus(200).SetBytes(DateTime.Now.ToString());
}));
http.Handlers.Add(new HTTPGetHandler("/useragent", (req, res, next) =>
{
res.SetStatus(200).SetBytes(req.Headers.GetValueOrDefault("User-Agent", "none"));
}));
http.Handlers.Add(new HTTPPostHandler("/search", (req, res, next) =>
{
res.SetStatus(200).SetBytes(req.Params != null ? $"Hi {req.Params.Get("username","someoneidk")} to {req.Params.Get("place","someplaceidk")}" : "absolute error -> " + req.Content);
}));
//http.Handlers.Add(new HTTPConditionHandler((req, res) => { return false; }, new HTTPStaticFilesHandler("smol")));
http.Handlers.Add(new HTTPStaticFilesHandler("htdocs"));
/*http.Handlers.Add(new HTTPGetHandler("/2", (req, res, next) =>
{
res.SetStatus(200).SetBytes("Hello! This is a second test!");
}));
http.Handlers.Add(new HTTPServeFileHandler("/favicon.ico", "favicon.ico",GetMimeType("ico")));*/
http.Listen();
}
private static string GetStatusFromNum(int v)
{
return v switch
{
200 => "200 OK",
400 => "400 Bad Request",
401 => "401 Unauthorized",
403 => "403 Forbidden",
404 => "404 Not Found",
_ => "501 Not Implemented"
};
}
private static string GetMimeType(string v)
{
/*
image/apng: Animated Portable Network Graphics (APNG)
image/avif : AV1 Image File Format (AVIF)
image/gif: Graphics Interchange Format (GIF)
image/jpeg: Joint Photographic Expert Group image (JPEG)
image/png: Portable Network Graphics (PNG)
image/svg+xml: Scalable Vector Graphics (SVG)
image/webp: Web Picture format (WEBP)
*/
return v switch
{
// text
"txt" => "text/plain",
"css" => "text/css",
"js" => "text/javascript",
"html" => "text/html",
"shtml" => "text/html",
"json" => "application/json",
// image
"webp" => "image/webp",
"png" => "image/png",
"jpg" or "jpeg" => "image/jpeg",
"svg" => "image/svg+xml",
"gif" => "image/gif",
"ico" => "image/x-icon",
// just download it man
_ => "application/octet-stream"
};
}
}
}