Compare commits

...

2 Commits

Author SHA1 Message Date
Mike Nolan 1a16846d7d Doing some fixup 2024-03-30 20:01:37 -05:00
Mike Nolan d07cd21b98 Doing some fixup 2024-03-30 20:01:24 -05:00
8 changed files with 401 additions and 13 deletions

View File

@ -2,13 +2,14 @@
[![Tesses.WebServer Nuget](https://badgen.net/nuget/v/Tesses.WebServer)](https://www.nuget.org/packages/Tesses.WebServer/) [![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) ![Tesses.WebServer Downloads](https://badgen.net/nuget/dt/Tesses.WebServer)
# License # License
Starting with 1.0.3.9 this library will use GPL-3.0 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 If you can not use GPL either use 1.0.3.8 or use another library
A TcpListener HTTP Server 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: 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) - 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: 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/)

View File

@ -1,5 +1,7 @@
using Tesses; using System.Drawing;
using Tesses;
using Tesses.WebServer; using Tesses.WebServer;
using Tesses.WebServer.HtmlLayout;
namespace Tesses.WebServer.ConsoleApp namespace Tesses.WebServer.ConsoleApp
{ {
class JsonObj class JsonObj
@ -8,10 +10,60 @@ namespace Tesses.WebServer.ConsoleApp
public DateTime Birthday {get;set;}=DateTime.Now; 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 class MainClass
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
TestObject some_object = new TestObject(); TestObject some_object = new TestObject();
RouteServer rserver = new RouteServer(); RouteServer rserver = new RouteServer();
rserver.Add("/", async(ctx) => { rserver.Add("/", async(ctx) => {
@ -20,6 +72,43 @@ namespace Tesses.WebServer.ConsoleApp
rserver.Add("/page", async(ctx) => { rserver.Add("/page", async(ctx) => {
await ctx.SendTextAsync("Demetria Devonne Lovato 8/20/1992"); 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)=>{ rserver.Add("/jsonEndpoint",async(ctx)=>{
var res=await ctx.ReadJsonAsync<JsonObj>(); var res=await ctx.ReadJsonAsync<JsonObj>();

View File

@ -2,6 +2,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Tesses.WebServer.NetStandard\Tesses.WebServer.NetStandard.csproj" /> <ProjectReference Include="..\Tesses.WebServer.NetStandard\Tesses.WebServer.NetStandard.csproj" />
<ProjectReference Include="..\Tesses.WebServer.HtmlLayout\Tesses.WebServer.HtmlLayout.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -3,6 +3,7 @@ using System.IO;
using System; using System;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading;
namespace Tesses.WebServer namespace Tesses.WebServer
{ {
@ -71,6 +72,17 @@ internal class SizedStream : Stream
} }
public class ServerContext 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 = "<>?/\\\"*|:"; const string bad_chars = "<>?/\\\"*|:";
public static string FixFileName(string filename,bool requireAscii=false) public static string FixFileName(string filename,bool requireAscii=false)
{ {

View File

@ -5,9 +5,11 @@ using System.Linq;
using System.Net; using System.Net;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using HeyRed.Mime; using HeyRed.Mime;
using Newtonsoft.Json;
namespace Tesses.WebServer namespace Tesses.WebServer
{ {
@ -200,8 +202,7 @@ namespace Tesses.WebServer
} }
ctx.ResponseHeaders.Add("Content-Length", (end - start + 1).ToString()); ctx.ResponseHeaders.Add("Content-Length", (end - start + 1).ToString());
ctx.ResponseHeaders.Add("Content-Type", contentType); ctx.WithMimeType(contentType);
await ctx.WriteHeadersAsync(); await ctx.WriteHeadersAsync();
if (!ctx.Method.Equals("HEAD", StringComparison.Ordinal)) if (!ctx.Method.Equals("HEAD", StringComparison.Ordinal))
{ {
@ -537,6 +538,11 @@ namespace Tesses.WebServer
} }
return files; 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()}"));
}
/// <summary> /// <summary>
/// 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. /// 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.
/// </summary> /// </summary>
@ -568,6 +574,7 @@ namespace Tesses.WebServer
} }
} }
public sealed class HttpFileResponseEntry public sealed class HttpFileResponseEntry
{ {
public HttpFileResponseEntry(string path, string filename, string fieldname, string contype) public HttpFileResponseEntry(string path, string filename, string fieldname, string contype)
@ -584,8 +591,10 @@ namespace Tesses.WebServer
public string Path {get;} public string Path {get;}
public string FieldName {get;} public string FieldName {get;}
[JsonIgnore]
public FileInfo FileInfo => new FileInfo(Path); public FileInfo FileInfo => new FileInfo(Path);
[JsonIgnore]
public object PrivateData {get;set;}=null;
public Stream OpenRead() public Stream OpenRead()
{ {
@ -606,10 +615,16 @@ namespace Tesses.WebServer
public IReadOnlyList<HttpFileResponseEntry> Files {get;} public IReadOnlyList<HttpFileResponseEntry> Files {get;}
public string Directory {get;} public string Directory {get;}
public void Dispose() public void Dispose()
{ {
if(System.IO.Directory.Exists(Directory))
System.IO.Directory.Delete(Directory,true); System.IO.Directory.Delete(Directory,true);
} }
~HttpFileResponse()
{
Dispose();
}
} }
/// <summary> /// <summary>

View File

@ -5,14 +5,16 @@
<PackageId>Tesses.WebServer</PackageId> <PackageId>Tesses.WebServer</PackageId>
<Author>Mike Nolan</Author> <Author>Mike Nolan</Author>
<Company>Tesses</Company> <Company>Tesses</Company>
<Version>1.0.4.2</Version> <Version>1.0.4.3</Version>
<AssemblyVersion>1.0.4.2</AssemblyVersion> <AssemblyVersion>1.0.4.3</AssemblyVersion>
<FileVersion>1.0.4.2</FileVersion> <FileVersion>1.0.4.3</FileVersion>
<Description>A TCP Listener HTTP(s) Server</Description> <Description>A TCP Listener HTTP(s) Server</Description>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>HTTP, WebServer, Website</PackageTags> <PackageTags>HTTP, WebServer, Website</PackageTags>
<RepositoryUrl>https://gitlab.tesses.net/tesses50/tesses.webserver</RepositoryUrl> <RepositoryUrl>https://gitea.site.tesses.net/tesses50/tesses.webserver</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>http-icon.png</PackageIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -20,5 +22,8 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Tesses.VirtualFileSystem.Base" Version="1.0.0" /> <PackageReference Include="Tesses.VirtualFileSystem.Base" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="..\http-icon.png" Pack="true" PackagePath="\" />
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project> </Project>

View File

@ -15,6 +15,7 @@ using System.Security.Authentication;
using System.Web; using System.Web;
using Tesses.VirtualFilesystem; using Tesses.VirtualFilesystem;
using System.Net.Mime; using System.Net.Mime;
using System.Numerics;
namespace Tesses.WebServer namespace Tesses.WebServer
{ {
@ -43,6 +44,41 @@ namespace Tesses.WebServer
public static class Extensions 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) public static ServerContext WithStatusCode(this ServerContext ctx, HttpStatusCode statusCode)
{ {
ctx.StatusCode = (int)statusCode; ctx.StatusCode = (int)statusCode;
@ -116,7 +152,11 @@ namespace Tesses.WebServer
try{ try{
EventHandler<SendEventArgs> cb= (sender,e0)=>{ EventHandler<SendEventArgs> cb= (sender,e0)=>{
if(__connected) if(__connected)
{
ctx.NetworkStream.Write($"data: {e0.Data}\n\n"); ctx.NetworkStream.Write($"data: {e0.Data}\n\n");
ctx.NetworkStream.Flush();
}
}; };
evt.EventReceived += cb; evt.EventReceived += cb;
while(ctx.Connected); while(ctx.Connected);
@ -483,6 +523,229 @@ namespace Tesses.WebServer
value = default(T2); value = default(T2);
return false; return false;
} }
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, object value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, int value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, short value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, long value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, Guid value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, byte value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, sbyte value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, uint value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, ushort value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, ulong value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, bool value)
{
args.Add(key,value ? "true" : "false");
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, float value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, double value)
{
args.Add(key,value.ToString());
}
public static void Add<T1>(this Dictionary<T1,List<string>> args,T1 key, decimal value)
{
args.Add(key,value.ToString());
}
public static bool TryGetFirstInt64<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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<T1>(this Dictionary<T1,List<string>> 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;
}
/// <summary> /// <summary>
/// Add item to the Dictionary<T1,List<T2>> with specified key (will create key in dictionary if not exist) /// Add item to the Dictionary<T1,List<T2>> with specified key (will create key in dictionary if not exist)
/// </summary> /// </summary>

BIN
http-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB