using System; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using System.IO; using System.Threading; using System.Text; using System.Collections.Generic; using System.Linq; using HeyRed.Mime; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace Tesses.WebServer { public static class Extensions { public static async Task WriteHeadersAsync(this ServerContext ctx) { string status_line = $"HTTP/1.1 {ctx.StatusCode} {StatusCodeMap.GetStatusString(ctx.StatusCode)}\r\n"; StringBuilder b = new StringBuilder(status_line); foreach (var hdr in ctx.ResponseHeaders) { foreach (var v in hdr.Value) { b.Append($"{hdr.Key}: {v}\r\n"); } } b.Append("\r\n"); var data = Encoding.UTF8.GetBytes(b.ToString()); await ctx.NetworkStream.WriteAsync(data, 0, data.Length); } public static async Task SendFileAsync(this ServerContext ctx, string file) { using (var strm = File.OpenRead(file)) { await ctx.SendStreamAsync( strm, MimeTypesMap.GetMimeType(file)); } } public static async Task SendExceptionAsync(this ServerContext ctx, Exception ex) { string name = ex.GetType().FullName; string j = $"{WebUtility.HtmlEncode(name)} thrown

{WebUtility.HtmlEncode(name)} thrown

Description: {WebUtility.HtmlEncode(ex.Message)}

"; ctx.StatusCode = 500; await ctx.SendTextAsync(j); } public static async Task SendJsonAsync(this ServerContext ctx,object value) { await ctx.SendTextAsync(JsonConvert.SerializeObject(value), "application/json"); } public static async Task SendTextAsync(this ServerContext ctx, string data, string contentType = "text/html") { await ctx.SendBytesAsync(Encoding.UTF8.GetBytes(data), contentType); } public static async Task SendBytesAsync(this ServerContext ctx, byte[] array, string contentType = "application/octet-stream") { using (var ms = new MemoryStream(array)) { await ctx.SendStreamAsync( ms, contentType); } } public static T2 GetFirst(this Dictionary> args,T1 key) { return args[key][0]; } public static bool TryGetFirst(this Dictionary> args,T1 key,out T2 value) { List ls; if (args.TryGetValue(key,out ls)) { if(ls.Count > 0) { value = ls[0]; return true; } } value = default(T2); return false; } public static void Add(this Dictionary> list,T1 key,T2 item) { if (list.ContainsKey(key)) { list[key].Add(item); } else { List items = new List(); items.Add(item); list.Add(key, items); } } public static void AddRange(this Dictionary> list,T1 key,IEnumerable items) { if (list.ContainsKey(key)) { list[key].AddRange(items); } else { List items2 = new List(); items2.AddRange(items); list.Add(key, items2); } } public static bool EndsWith(this StringBuilder sb, string test, StringComparison comparison) { if (sb.Length < test.Length) return false; string end = sb.ToString(sb.Length - test.Length, test.Length); return end.Equals(test, comparison); } } public class NotFoundServer : Server { public NotFoundServer(string html) { _html = html; } public NotFoundServer() { _html = "File {url} not found

404 Not Found

{url}

"; } string _html; public override async Task GetAsync(ServerContext ctx) { ctx.StatusCode = 404; await ctx.SendTextAsync( _html.Replace("{url}", WebUtility.HtmlEncode(ctx.UrlPath))); } } public class StaticServer : Server { string _path; IServer _server; public StaticServer(string path) { _path = path; _server = new NotFoundServer(); _defaultFileNames = new string[] {"index.html","index.htm","default.html","default.htm" }; } string[] _defaultFileNames; public StaticServer(string path,string[] defaultFileNames,IServer notfoundserver) { _path = path; _server = notfoundserver; _defaultFileNames = defaultFileNames; } public bool DefaultFileExists(string path,out string name) { foreach(var def in _defaultFileNames) { name = Path.Combine(path, def); if(File.Exists(name)) { return true; } } name = ""; return false; } public override async Task GetAsync(ServerContext ctx) { string someUrl = Path.Combine(_path,WebUtility.UrlDecode(ctx.UrlPath.Substring(1)).Replace('/', Path.DirectorySeparatorChar)); Console.WriteLine(someUrl); if (Directory.Exists(someUrl)) { string name; if(DefaultFileExists(someUrl,out name)) { await ctx.SendFileAsync(name); } } else if (File.Exists(someUrl)) { await ctx.SendFileAsync(someUrl); } else { await _server.GetAsync(ctx); } } } public abstract class Server : IServer { public static readonly NotFoundServer ServerNull = new NotFoundServer(); public IServer Guaranteed(IServer svr) { if(svr != null) { return svr; } return ServerNull; } public bool CorsHeader = true; public abstract Task GetAsync(ServerContext ctx); public virtual async Task PostAsync(ServerContext ctx) { ctx.StatusCode = (int)HttpStatusCode.MethodNotAllowed; await ctx.SendTextAsync("Method Not Supported"); } public virtual async Task OptionsAsync(ServerContext ctx) { await ctx.WriteHeadersAsync(); ctx.NetworkStream.Close(); } public virtual async Task OtherAsync(ServerContext ctx) { ctx.StatusCode = (int)HttpStatusCode.MethodNotAllowed; await ctx.SendTextAsync("Method Not Supported"); } public virtual async Task BeforeAsync(ServerContext ctx) { return await Task.FromResult(false); } public void AddCors(ServerContext ctx) { if (CorsHeader) { ctx.ResponseHeaders.Add("Access-Control-Allow-Origin", "*"); ctx.ResponseHeaders.Add("Access-Control-Allow-Headers", "Cache-Control, Pragma, Accept, Origin, Authorization, Content-Type, X-Requested-With"); ctx.ResponseHeaders.Add("Access-Control-Allow-Methods", "GET, POST"); ctx.ResponseHeaders.Add("Access-Control-Allow-Credentials", "true"); } } } public sealed class MountableServer : Server { Dictionary _servers = new Dictionary(); public MountableServer(IServer root) { _root = root; } IServer _root; private (string Key,IServer Value) GetFromPath(ServerContext ctx) { //bool j = false; foreach(var item in _servers.Reverse()) { if(ctx.UrlPath.StartsWith(item.Key,StringComparison.Ordinal)) { return (item.Key,Guaranteed(item.Value)); } if (ctx.UrlPath == item.Key.TrimEnd('/')) { ctx.UrlPath += "/"; return (item.Key,Guaranteed(item.Value)); } } //Console.WriteLine("HERE WE ARE"); return ("/",Guaranteed(_root)); } /// /// Mount the specified url and server. /// Must mount like this /// /somePath0 /// /somePath0/someSubPath0 /// /somePath0/someSubPath0/someSubSubPath0 /// /somePath0/someSubPath0/someSubSubPath1 /// /somePath0/someSubPath1 /// /somePath0/someSubPath1/someSubSubPath0 /// /somePath0/someSubPath1/someSubSubPath1 /// /// URL. /// Server. public void Mount(string url,IServer server) { _servers.Add(url, server); } public void Unmount(string url) { _servers.Remove(url); } public void UnmountAll() { _servers.Clear(); } public override async Task GetAsync(ServerContext ctx) { var v = GetFromPath(ctx); string url = '/' + ctx.UrlPath.Substring(v.Key.Length).TrimStart('/'); ctx.UrlPath = url; await v.Value.GetAsync(ctx); } public override async Task PostAsync(ServerContext ctx) { var v = GetFromPath(ctx); string url = '/' + ctx.UrlPath.Substring(v.Key.Length).TrimStart('/'); ctx.UrlPath = url; await v.Value.PostAsync(ctx); } public override async Task BeforeAsync(ServerContext ctx) { var v = GetFromPath(ctx); string old=ctx.UrlPath; string url = '/' + ctx.UrlPath.Substring(v.Key.Length).TrimStart('/'); ctx.UrlPath = url; var res=await v.Value.BeforeAsync(ctx); ctx.UrlPath = old; return res; } public override async Task OptionsAsync(ServerContext ctx) { var v = GetFromPath(ctx); string url = '/' + ctx.UrlPath.Substring(v.Key.Length).TrimStart('/'); ctx.UrlPath = url; await v.Value.OptionsAsync(ctx); } public override async Task OtherAsync(ServerContext ctx) { var v = GetFromPath(ctx); string url = '/' + ctx.UrlPath.Substring(v.Key.Length).TrimStart('/'); ctx.UrlPath = url; await v.Value.OtherAsync(ctx); } } public delegate bool Authenticate(string username, string password); public class BasicAuthServer : Server { public BasicAuthServer(Authenticate auth,IServer inner,string realm="SampleRealm") { Authenticate = auth; InnerServer = inner; Realm = realm; } public override async Task BeforeAsync(ServerContext ctx) { if(await Authorize(ctx)) { return await Guaranteed(InnerServer).BeforeAsync(ctx); } return true; } public override async Task GetAsync(ServerContext ctx) { await Guaranteed(InnerServer).GetAsync(ctx); } public override async Task PostAsync(ServerContext ctx) { await Guaranteed(InnerServer).PostAsync(ctx); } public override async Task OtherAsync(ServerContext ctx) { await Guaranteed(InnerServer).OtherAsync(ctx); } public override async Task OptionsAsync(ServerContext ctx) { await Guaranteed(InnerServer).OptionsAsync(ctx); } public IServer InnerServer { get; set; } public Authenticate Authenticate { get; set; } public string Realm { get; set; } private bool ValidAuth(ServerContext ctx) { string auth; if (ctx.RequestHeaders.TryGetFirst("Authorization", out auth)) { string[] authorization = auth.Split(' '); //authorization_basic if (authorization[0] == "Basic") { string[] userPass = Encoding.UTF8.GetString(Convert.FromBase64String(authorization[1])).Split(new char[] { ':' },2); //return userPass.Equals($"{config.UserName}:{config.Password}", StringComparison.Ordinal); return Authenticate(userPass[0], userPass[1]); } } return false; } private async Task Authorize(ServerContext ctx) { if (Authenticate == null) return true; if (ValidAuth(ctx)) return true; ctx.ResponseHeaders.Add("WWW-Authenticate", $"Basic realm=\"{Realm}\""); ctx.StatusCode = 401; await UnauthorizedPage(ctx); return false; } protected virtual async Task UnauthorizedPage(ServerContext ctx) { await ctx.SendTextAsync("Unauthorized"); } } public class HostDomainServer : Server { public HostDomainServer(IServer alt) { Default = alt; Servers = new Dictionary(); } public void Clear() { Servers.Clear(); } public void Remove(string fqdn_or_ip) { Servers.Remove(fqdn_or_ip); } public IServer Default { get; set; } Dictionary Servers; public void AddDomain(string fqdn_or_ip,IServer svr) { Servers.Add(fqdn_or_ip, svr); } public override async Task BeforeAsync(ServerContext ctx) { return await GetDomain(ctx).BeforeAsync(ctx); } public override async Task PostAsync(ServerContext ctx) { await GetDomain(ctx).PostAsync(ctx); } public override async Task OtherAsync(ServerContext ctx) { await GetDomain(ctx).OtherAsync(ctx); } public override async Task OptionsAsync(ServerContext ctx) { await GetDomain(ctx).OptionsAsync(ctx); } public override async Task GetAsync(ServerContext ctx) { await GetDomain(ctx).GetAsync(ctx); } private IServer GetDomain(ServerContext ctx) { string fqdn_or_ip = ctx.Host; foreach(var item in Servers) { if(item.Key.Equals(fqdn_or_ip,StringComparison.Ordinal)) { return Guaranteed(item.Value); } } return Guaranteed(Default); } } public interface IServer { void AddCors(ServerContext ctx); Task BeforeAsync(ServerContext ctx); Task GetAsync(ServerContext ctx); Task PostAsync(ServerContext ctx); Task OptionsAsync(ServerContext ctx); Task OtherAsync(ServerContext ctx); } public sealed class HttpServerListener { bool https; X509Certificate cert; IServer _server; TcpListener _listener; public HttpServerListener(IPEndPoint endPoint,IServer server) { _listener = new TcpListener(endPoint); _server = server; https = false; } public HttpServerListener(IServer server) { _listener = new TcpListener(new IPEndPoint(IPAddress.Any, 3251)); _server = server; https = false; } public HttpServerListener(IPEndPoint endpoint,IServer server,X509Certificate cert) { _listener = new TcpListener(endpoint); _server = server; https = cert != null; this.cert = cert; } public async Task ListenAsync(CancellationToken token) { _listener.Start(); using (var r = token.Register(() => _listener.Stop())) { while (!token.IsCancellationRequested) { var socket=await _listener.AcceptTcpClientAsync(); await CommunicateHostAsync(socket).ConfigureAwait(false); } } } private string ReadHeaders(Stream strm) { StringBuilder s = new StringBuilder(); var decoder = Encoding.UTF8.GetDecoder(); var nextChar = new char[1]; while (!s.EndsWith("\r\n\r\n",StringComparison.Ordinal)) { int data = strm.ReadByte(); if(data == -1) { break; } int charCount=decoder.GetChars(new byte[] { (byte)data }, 0, 1, nextChar, 0); if (charCount == 0) continue; s.Append(nextChar); } return s.ToString(); } private Dictionary> Headers(string s,out string req_line) { Dictionary> items = new Dictionary>(); string[] lines = s.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); req_line = lines[0]; for(int i=1;i HTTP/1.1\r\n //HEADER1\r\n //HEADER2\r\n //...... //HEADERN\r\n //\r\n //OPTIONAL REQUEST BODY //RESPONSE using (Stream strm = GetStream(clt)) { string request_line = ""; string res=ReadHeaders(strm); var headers=Headers(res,out request_line); // {Method} {Path} HTTP/1.1 ServerContext ctx=null; string[] request=request_line.Split(new char[] { ' ' }, 3); string method = request[0]; try { string path = request[1]; string ver = request[2]; ctx = new ServerContext(method, strm, path, headers); ctx.Server = clt.Client.LocalEndPoint as IPEndPoint; ctx.Client = clt.Client.RemoteEndPoint as IPEndPoint; _server.AddCors(ctx); if (!await _server.BeforeAsync(ctx)) { switch (method) { case "HEAD": case "GET": await _server.GetAsync(ctx); break; case "POST": await _server.PostAsync(ctx); break; case "OPTIONS": await _server.OptionsAsync(ctx); break; default: await _server.OtherAsync(ctx); break; } } }catch(Exception ex) { try { await ctx.SendExceptionAsync(ex); }catch(Exception ex2) { _ = ex2; } } } } //protected abstract Task Get(string url,Dictionary headers); //protected abstract Task GetAsync(ServerContext ctx); } }