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, HEAD = 2,// 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 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; else if (splitfirst[0] == "HEAD") Method = HTTPMeth.HEAD; 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"); public bool isHead = false; string Status = GetStatusFromNum(200); DateTime creationDate = DateTime.Now; 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 isHead ? Encoding.UTF8.GetBytes( $"HTTP/1.1 {Status}\r\n" + $"Date: {creationDate.ToUniversalTime()}\r\n"+ $"Content-Length: {contentBytes.Length}\r\n" + $"Content-Type: {ContentType}\r\n" ) : Encoding.UTF8.GetBytes( $"HTTP/1.1 {Status}\r\n" + $"Content-Type: {ContentType}\n" + $"Content-Length: {contentBytes.Length}" + $"\n" + $"\n" ).Concat(contentBytes).ToArray(); } } public class HTTPParams { public Dictionary 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 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.Method == HTTPMeth.GET && request.Path == Path) Action(request, response, next); else next(); } } public class HTTPPostHandler : HTTPHandler { string Path = ""; Action Action = (HTTPReq req, HTTPRes res, Action next) => { }; public HTTPPostHandler(string path, 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 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) { 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(); res.isHead = req.Method == HTTPMeth.HEAD; 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(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" }; } } }