From d07cd21b9877e0763f20aa31d5769a6a19c83c1a Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Sat, 30 Mar 2024 20:01:24 -0500 Subject: [PATCH] Doing some fixup --- README.md | 5 +- Tesses.WebServer.Console/Program.cs | 91 +++++- .../Tesses.WebServer.Console.csproj | 1 + Tesses.WebServer.NetStandard/ServerContext.cs | 12 + .../SimpleHttpCode.cs | 23 +- .../Tesses.WebServer.NetStandard.csproj | 15 +- Tesses.WebServer.NetStandard/TessesServer.cs | 267 +++++++++++++++++- 7 files changed, 401 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index dd5761f..6cf68bb 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ [![Tesses.WebServer Nuget](https://badgen.net/nuget/v/Tesses.WebServer)](https://www.nuget.org/packages/Tesses.WebServer/) ![Tesses.WebServer Downloads](https://badgen.net/nuget/dt/Tesses.WebServer) + # License Starting with 1.0.3.9 this library will use GPL-3.0 If you can not use GPL either use 1.0.3.8 or use another library A TcpListener HTTP Server -WARNING: use at least version 1.0.4.2 because of security issue with paths +> WARNING: use at least version 1.0.4.2 because of security issue with paths To make your life easier, install [Tesses.WebServer.EasyServer](https://www.nuget.org/packages/Tesses.WebServer.EasyServer) alongside [Tesses.WebServer](https://www.nuget.org/packages/Tesses.WebServer) and use this code: @@ -50,3 +51,5 @@ server.StartServer(9500); //or any port number - Reverse Proxy (in a seperate library) > Note: Range code, POST code and Route Class is not mine its a modified version of the code from ( [dajuric/simple-http](https://github.com/dajuric/simple-http/blob/master/Source/SimpleHTTP/Extensions/Response/ResponseExtensions.PartialStream.cs "dajuric/simple-http")) + +> Note the nuget icon is from [here](https://uxwing.com/http-icon/) diff --git a/Tesses.WebServer.Console/Program.cs b/Tesses.WebServer.Console/Program.cs index 9419fb4..4fd9566 100644 --- a/Tesses.WebServer.Console/Program.cs +++ b/Tesses.WebServer.Console/Program.cs @@ -1,5 +1,7 @@ -using Tesses; +using System.Drawing; +using Tesses; using Tesses.WebServer; +using Tesses.WebServer.HtmlLayout; namespace Tesses.WebServer.ConsoleApp { class JsonObj @@ -8,10 +10,60 @@ namespace Tesses.WebServer.ConsoleApp public DateTime Birthday {get;set;}=DateTime.Now; } + public class MyOther + { + [FormNewLine] + + public string Hello {get;set;}=""; + [FormNewLine] + + public Color FavoriteColor {get;set;}=Color.Pink; + // [FormNewLine] + + //public HttpFileResponseEntry[] Files {get;set;}=new HttpFileResponseEntry[0]; + } + public enum TestEnum + { + Apple, + Orange, + Grape, + Banana, + Raspberry, + + Blueberry, + Strawberry + } + class Test + { + [FormNewLine] + [FormText("Your Name",Placeholder="Name",Name="name")] + public string Name {get;set;}=""; + [FormNewLine] + [FormText("Describe yourself",Placeholder="Description",Name="description")] + public string Description {get;set;}=""; + [FormNewLine] + [FormCheckbox("Are you an adult")] + public bool Adult {get;set;}=false; + [FormNewLine] + [FormCheckbox("Email Me")] + public bool EmailMe {get;set;}=true; + + [FormNewLine] + [FormRadio("fruit")] + public TestEnum Fruit {get;set;}=TestEnum.Raspberry; + + [FormNewLine] + [FormFieldSet] + public MyOther MyOther {get;set;}=new MyOther(); + + + } + class MainClass { public static void Main(string[] args) { + TestObject some_object = new TestObject(); RouteServer rserver = new RouteServer(); rserver.Add("/", async(ctx) => { @@ -20,6 +72,43 @@ namespace Tesses.WebServer.ConsoleApp rserver.Add("/page", async(ctx) => { await ctx.SendTextAsync("Demetria Devonne Lovato 8/20/1992"); }); + rserver.Add("/john",async(ctx)=>{ + Test other = new Test(); + ctx.ParseSmartForm(other); + + await ctx.SendJsonAsync(other); + },"POST"); + rserver.Add("/html_ex",async(ctx)=>{await ctx.SendHtmlAsync(H.Html( + H.Head( + H.Meta().WithAttribute("charset","UTF-8"), + H.Meta().WithAttribute("name","viewport").WithAttribute("content","width=device-width, initial-scale=1.0"), + H.Title("Document") + ), + H.Body(H.Form("./john",new Test(),true)) + ).WithAttribute("lang","en"));}); + rserver.Add("/absolute_paths",(ctx)=>{ + using(var sw = ctx.GetResponseStreamWriter()) + { + sw.WriteLine($"Root: {ctx.GetRealRootUrl()}"); + sw.WriteLine($"Current Server Root: {ctx.GetCurrentServerPath()}"); + sw.WriteLine($"Demetria: {ctx.GetRealUrlRelativeToCurrentServer("/page")}"); + sw.WriteLine($"Headers: {ctx.GetRealUrlRelativeToCurrentServer("/headers")}"); + sw.WriteLine($"Relative To Root: {ctx.GetRealUrl("/johnconnor/")}"); + } + }); + + rserver.Add("/headers",(ctx)=>{ + using(var sw = ctx.GetResponseStreamWriter()) + { + foreach(var item in ctx.RequestHeaders) + { + foreach(var item2 in item.Value) + { + sw.WriteLine($"{item.Key}: {item2}"); + } + } + } + }); rserver.Add("/jsonEndpoint",async(ctx)=>{ var res=await ctx.ReadJsonAsync(); diff --git a/Tesses.WebServer.Console/Tesses.WebServer.Console.csproj b/Tesses.WebServer.Console/Tesses.WebServer.Console.csproj index 263c165..8c7fbc1 100644 --- a/Tesses.WebServer.Console/Tesses.WebServer.Console.csproj +++ b/Tesses.WebServer.Console/Tesses.WebServer.Console.csproj @@ -2,6 +2,7 @@ + diff --git a/Tesses.WebServer.NetStandard/ServerContext.cs b/Tesses.WebServer.NetStandard/ServerContext.cs index b6bd0d8..0d52c66 100644 --- a/Tesses.WebServer.NetStandard/ServerContext.cs +++ b/Tesses.WebServer.NetStandard/ServerContext.cs @@ -3,6 +3,7 @@ using System.IO; using System; using System.Net; using System.Text; +using System.Threading; namespace Tesses.WebServer { @@ -71,6 +72,17 @@ internal class SizedStream : Stream } public class ServerContext { + static Mutex mtx=new Mutex(); + static long _unique=0; + + public static long UniqueNumber() + { + mtx.WaitOne(); + long u=_unique++; + mtx.ReleaseMutex(); + return u; + } + const string bad_chars = "<>?/\\\"*|:"; public static string FixFileName(string filename,bool requireAscii=false) { diff --git a/Tesses.WebServer.NetStandard/SimpleHttpCode.cs b/Tesses.WebServer.NetStandard/SimpleHttpCode.cs index f6199c6..08ab4e9 100644 --- a/Tesses.WebServer.NetStandard/SimpleHttpCode.cs +++ b/Tesses.WebServer.NetStandard/SimpleHttpCode.cs @@ -5,9 +5,11 @@ using System.Linq; using System.Net; using System.Runtime.CompilerServices; using System.Text; + using System.Text.RegularExpressions; using System.Threading.Tasks; using HeyRed.Mime; +using Newtonsoft.Json; namespace Tesses.WebServer { @@ -200,8 +202,7 @@ namespace Tesses.WebServer } ctx.ResponseHeaders.Add("Content-Length", (end - start + 1).ToString()); - ctx.ResponseHeaders.Add("Content-Type", contentType); - + ctx.WithMimeType(contentType); await ctx.WriteHeadersAsync(); if (!ctx.Method.Equals("HEAD", StringComparison.Ordinal)) { @@ -537,6 +538,11 @@ namespace Tesses.WebServer } return files; } + public static HttpFileResponse ParseBodyWithTempDirectory(this ServerContext request) + { + DateTime dt=DateTime.Now; + return request.ParseBodyWithTempDirectory(Path.Combine(Path.GetTempPath(),$"TWSUPLOAD_{dt.ToString("yyyyMMdd_HHmmss")}_{ServerContext.UniqueNumber()}")); + } /// /// Parses body of the request including form and multi-part form data, allowing multiple file with same key and storing the files in a temp directory specified by the user. /// @@ -568,6 +574,7 @@ namespace Tesses.WebServer } } + public sealed class HttpFileResponseEntry { public HttpFileResponseEntry(string path, string filename, string fieldname, string contype) @@ -584,8 +591,10 @@ namespace Tesses.WebServer public string Path {get;} public string FieldName {get;} - + [JsonIgnore] public FileInfo FileInfo => new FileInfo(Path); + [JsonIgnore] + public object PrivateData {get;set;}=null; public Stream OpenRead() { @@ -606,9 +615,15 @@ namespace Tesses.WebServer public IReadOnlyList Files {get;} public string Directory {get;} + public void Dispose() { - System.IO.Directory.Delete(Directory,true); + if(System.IO.Directory.Exists(Directory)) + System.IO.Directory.Delete(Directory,true); + } + ~HttpFileResponse() + { + Dispose(); } } diff --git a/Tesses.WebServer.NetStandard/Tesses.WebServer.NetStandard.csproj b/Tesses.WebServer.NetStandard/Tesses.WebServer.NetStandard.csproj index 61b9c2e..85cf10c 100644 --- a/Tesses.WebServer.NetStandard/Tesses.WebServer.NetStandard.csproj +++ b/Tesses.WebServer.NetStandard/Tesses.WebServer.NetStandard.csproj @@ -5,14 +5,16 @@ Tesses.WebServer Mike Nolan Tesses - 1.0.4.2 - 1.0.4.2 - 1.0.4.2 + 1.0.4.3 + 1.0.4.3 + 1.0.4.3 A TCP Listener HTTP(s) Server GPL-3.0-only true HTTP, WebServer, Website - https://gitlab.tesses.net/tesses50/tesses.webserver + https://gitea.site.tesses.net/tesses50/tesses.webserver + README.md + http-icon.png @@ -20,5 +22,8 @@ - + + + + diff --git a/Tesses.WebServer.NetStandard/TessesServer.cs b/Tesses.WebServer.NetStandard/TessesServer.cs index 277831c..65ab494 100644 --- a/Tesses.WebServer.NetStandard/TessesServer.cs +++ b/Tesses.WebServer.NetStandard/TessesServer.cs @@ -15,6 +15,7 @@ using System.Security.Authentication; using System.Web; using Tesses.VirtualFilesystem; using System.Net.Mime; +using System.Numerics; namespace Tesses.WebServer { @@ -43,6 +44,41 @@ namespace Tesses.WebServer public static class Extensions { + public static string GetRealRootUrl(this ServerContext ctx) + { + if(ctx.RequestHeaders.TryGetFirst("X-Forwarded-Path",out var xfwp)) + { + return $"{xfwp.TrimEnd('/')}/"; + } + else if(ctx.RequestHeaders.TryGetFirst("X-Forwarded-Host",out var host)) + { + if(!ctx.RequestHeaders.TryGetFirst("X-Forwarded-Proto",out var proto)) proto="http"; + return $"{proto}://{host}/"; + } + else if(ctx.RequestHeaders.TryGetFirst("Host",out var theHost)) + { + return $"http://{theHost}/"; + } + return $"http://{ctx.Client}/"; + } + public static string GetRealUrl(this ServerContext ctx,string url) + { + return $"{ctx.GetRealRootUrl()}{url.TrimStart('/')}"; + } + public static string GetCurrentServerPath(this ServerContext ctx) + { + if(ctx.UrlPath == ctx.OriginalUrlPath) return "/"; + return $"{ctx.OriginalUrlPath.Remove(ctx.OriginalUrlPath.Length-ctx.UrlPath.Length).TrimEnd('/')}/"; + } + public static string GetRealUrlRelativeToCurrentServer(this ServerContext ctx, string url) + { + return ctx.GetRealUrl($"{ctx.GetCurrentServerPath()}{url.TrimStart('/')}"); + } + public static ServerContext WithStatusCode(this ServerContext ctx, int statusCode) + { + ctx.StatusCode = statusCode; + return ctx; + } public static ServerContext WithStatusCode(this ServerContext ctx, HttpStatusCode statusCode) { ctx.StatusCode = (int)statusCode; @@ -116,7 +152,11 @@ namespace Tesses.WebServer try{ EventHandler cb= (sender,e0)=>{ if(__connected) - ctx.NetworkStream.Write($"data: {e0.Data}\n\n"); + { + ctx.NetworkStream.Write($"data: {e0.Data}\n\n"); + ctx.NetworkStream.Flush(); + } + }; evt.EventReceived += cb; while(ctx.Connected); @@ -483,6 +523,229 @@ namespace Tesses.WebServer value = default(T2); return false; } + public static void Add(this Dictionary> args,T1 key, object value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, int value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, short value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, long value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, Guid value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, byte value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, sbyte value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, uint value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, ushort value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, ulong value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, bool value) + { + args.Add(key,value ? "true" : "false"); + } + public static void Add(this Dictionary> args,T1 key, float value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, double value) + { + args.Add(key,value.ToString()); + } + public static void Add(this Dictionary> args,T1 key, decimal value) + { + args.Add(key,value.ToString()); + } + + public static bool TryGetFirstInt64(this Dictionary> args,T1 key, out long value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= long.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstInt32(this Dictionary> args,T1 key, out int value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= int.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstInt16(this Dictionary> args,T1 key, out short value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= short.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstInt8(this Dictionary> args,T1 key, out sbyte value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= sbyte.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool GetFirstBoolean(this Dictionary> args,T1 key) + { + if(args.TryGetFirst(key,out var value)) + { + value=value.ToLower(); + if(value == "off" || value == "no" || value == "false" || value == "0") return false; + return true; + } + return false; + } + public static bool TryGetFirstGuid(this Dictionary> args,T1 key, out Guid value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= Guid.TryParse(str,out var val); + value = val; + return res; + } + value=Guid.Empty; + return false; + } + public static bool TryGetFirstUInt8(this Dictionary> args,T1 key, out byte value) + { + if(args.TryGetFirst(key,out var str)) + { + bool res= byte.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstUInt16(this Dictionary> args,T1 key, out ushort value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= ushort.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstUInt64(this Dictionary> args,T1 key, out ulong value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= ulong.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstUInt32(this Dictionary> args,T1 key, out uint value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= uint.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + + public static bool TryGetFirstFloat(this Dictionary> args,T1 key, out float value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= float.TryParse(str,out var val); + value = val; + return res; + } + value=0.0f; + return false; + } + public static bool TryGetFirstBigInteger(this Dictionary> args,T1 key, out BigInteger value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= BigInteger.TryParse(str,out var val); + value = val; + return res; + } + value=0; + return false; + } + public static bool TryGetFirstDecimal(this Dictionary> args,T1 key, out decimal value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= decimal.TryParse(str,out var val); + value = val; + return res; + } + value=0.0M; + return false; + } + public static bool TryGetFirstDouble(this Dictionary> args,T1 key, out double value) + { + + if(args.TryGetFirst(key,out var str)) + { + bool res= double.TryParse(str,out var val); + value = val; + return res; + } + value=0.0; + return false; + } /// /// Add item to the Dictionary> with specified key (will create key in dictionary if not exist) /// @@ -1948,7 +2211,7 @@ namespace Tesses.WebServer } count = Math.Min(count,this.buffer.Length-this.offset); Array.Copy(this.buffer,this.offset,buffer,offset,count); - + this.offset+=count; return count; }