diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..878a6e1 --- /dev/null +++ b/Program.cs @@ -0,0 +1,343 @@ +using System.IO.Compression; +using System.Net; +using System.Net.Sockets; +using System.Text; + + +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 Dictionary Headers = new Dictionary(); + + 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); + foreach(var item in arraycontent) + { + Content += item + "\r\n"; + } + } + break; + } + var esplit = e.Split(": "); + Headers.Add(esplit[0], esplit[1]); + + } + + valid = true; + } + catch (Exception e) + { + valid = false; + } + + } + + 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.1 {Status}\r\n" + + $"Content-Type: {ContentType}{(ContentType.Contains("text") ? "; charset=utf-8" : "")}\n" + + $"Content-Length: {contentBytes.Length}" + + $"\n" + + $"\n" + ).Concat(contentBytes).ToArray(); + } + } + 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 Action = (HTTPReq req, HTTPRes res, Action next) => { }; + public HTTPGetHandler(string path, Action action) { Path = path; Action = action; } + + public override void Handle(HTTPReq request, HTTPRes response, Action next) + { + if (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(File.ReadAllBytes(Files + Path.DirectorySeparatorChar + pat)).SetStatus(200).ContentType = GetMimeType(ex); + } + else next(); + } + } + public class HTTPConditionHandler : HTTPHandler + { + HTTPHandler Handler = null; + Func Condition = (HTTPReq request, HTTPRes response) => { return false; }; + public HTTPConditionHandler(Func 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 Handlers = new(); + + public void Listen() + { + listener.Start(); + + + while (true) + { + try + { + var a = listener.AcceptTcpClient(); + + 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()); + + } + else + { + + // Send back a response. + var nf = "uuh"; + var nfb = Encoding.UTF8.GetBytes(nf); + s.Write( + Encoding.UTF8.GetBytes( + $"HTTP/1.1 404 Not Found\n" + + $"Content-Type: text/plain; charset=utf-8\n" + + $"Content-Length: {nfb.Length}" + + $"\n" + + $"\n" + + $"{nf}" + ) + ); + } + } + + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + } + + + } + static void Main(string[] args) + { + Console.WriteLine("hello"); + + HTTPServer http = new(8003); + + http.Handlers.Add(new HTTPStaticFilesHandler("htdocs")); + + /*http.Handlers.Add(new HTTPGetHandler("/", (req, res, next) => + { + res.SetStatus(200).SetBytes("Hello! This is a test"); + })); + 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", + "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" + }; + } + } + +} diff --git a/myownhttp.csproj b/myownhttp.csproj new file mode 100644 index 0000000..fd4bd08 --- /dev/null +++ b/myownhttp.csproj @@ -0,0 +1,10 @@ + + + + Exe + net9.0 + enable + enable + + + diff --git a/myownhttp.sln b/myownhttp.sln new file mode 100644 index 0000000..f1f5ad9 --- /dev/null +++ b/myownhttp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36203.30 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "myownhttp", "myownhttp.csproj", "{4E977172-6187-481D-B819-982A278D925E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4E977172-6187-481D-B819-982A278D925E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E977172-6187-481D-B819-982A278D925E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E977172-6187-481D-B819-982A278D925E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E977172-6187-481D-B819-982A278D925E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FB36ECD5-0874-4AB9-A8BB-2AF5DE85A8F9} + EndGlobalSection +EndGlobal