450 lines
16 KiB
C#
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"
|
|
};
|
|
}
|
|
}
|
|
|
|
}
|