Initial commit
This commit is contained in:
parent
6b76c667b7
commit
21fa2c5af0
40
src/lib/MBS.Web/ClientConnectedEventArgs.cs
Normal file
40
src/lib/MBS.Web/ClientConnectedEventArgs.cs
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// ClientConnectedEventArgs.cs
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// Copyright (c) 2021 Mike Becker's Software
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class ClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
public bool Handled { get; set; } = false;
|
||||
|
||||
public WebRequest Request { get; } = null;
|
||||
public WebResponse Response { get; } = null;
|
||||
|
||||
public System.Net.Sockets.TcpClient Client { get; } = null;
|
||||
public ClientConnectedEventArgs(System.Net.Sockets.TcpClient client, WebRequest request, WebResponse response)
|
||||
{
|
||||
Client = client;
|
||||
Request = request;
|
||||
Response = response;
|
||||
}
|
||||
}
|
||||
33
src/lib/MBS.Web/ClientConnectingEventArgs.cs
Normal file
33
src/lib/MBS.Web/ClientConnectingEventArgs.cs
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// ClientConnectedEventArgs.cs
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// Copyright (c) 2021 Mike Becker's Software
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class ClientConnectingEventArgs : CancelEventArgs
|
||||
{
|
||||
public System.Net.Sockets.TcpClient Client { get; } = null;
|
||||
public ClientConnectingEventArgs(System.Net.Sockets.TcpClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
100
src/lib/MBS.Web/Control.cs
Normal file
100
src/lib/MBS.Web/Control.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System.Xml;
|
||||
using MBS.Web.UI;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public abstract class Control : IWebHandler
|
||||
{
|
||||
|
||||
public class ControlCollection
|
||||
: System.Collections.ObjectModel.Collection<Control>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Dictionary<string, string> PathVariables { get; } = new Dictionary<string, string>();
|
||||
public Control(Dictionary<string, string>? pathVariables = null)
|
||||
{
|
||||
if (pathVariables == null)
|
||||
pathVariables = new Dictionary<string, string>();
|
||||
|
||||
PathVariables = pathVariables;
|
||||
}
|
||||
|
||||
protected virtual void OnInit(RenderEventArgs e)
|
||||
{
|
||||
}
|
||||
protected virtual void RenderContents(XmlWriter writer)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual string TagName { get; } = "div";
|
||||
public Dictionary<string, string> Attributes { get; } = new Dictionary<string, string>();
|
||||
public CssClassList ClassList { get; } = new CssClassList();
|
||||
|
||||
private bool _initted = false;
|
||||
protected void EnsureInitialized()
|
||||
{
|
||||
if (_initted) return;
|
||||
InitializeInternal();
|
||||
_initted = true;
|
||||
}
|
||||
|
||||
protected virtual void InitializeInternal()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void RenderBeginTag(XmlWriter writer)
|
||||
{
|
||||
EnsureInitialized();
|
||||
writer.WriteStartElement(TagName);
|
||||
|
||||
bool classListWritten = false;
|
||||
IDictionary<string, string> attrs = GetControlAttributes();
|
||||
foreach (KeyValuePair<string, string> kvp in attrs)
|
||||
{
|
||||
string value = kvp.Value;
|
||||
if (kvp.Key == "class")
|
||||
{
|
||||
IEnumerable<string> classList = ClassList.Union(kvp.Value.Split(new char[] { ' ' }));
|
||||
value = String.Join(' ', classList);
|
||||
classListWritten = true;
|
||||
}
|
||||
writer.WriteAttributeString(kvp.Key, value);
|
||||
}
|
||||
|
||||
if (!classListWritten && ClassList.Count > 0)
|
||||
{
|
||||
writer.WriteAttributeString("class", String.Join(' ', ClassList));
|
||||
}
|
||||
}
|
||||
protected virtual void RenderEndTag(XmlWriter writer)
|
||||
{
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public void Render(XmlWriter writer)
|
||||
{
|
||||
RenderBeginTag(writer);
|
||||
RenderContents(writer);
|
||||
RenderEndTag(writer);
|
||||
}
|
||||
|
||||
public void ProcessRequest(WebContext context)
|
||||
{
|
||||
context.Response.ContentType = "application/xhtml+xml";
|
||||
|
||||
OnInit(new RenderEventArgs(context.Request, context.Response));
|
||||
|
||||
XmlWriter writer = XmlWriter.Create(context.Response.Stream);
|
||||
Render(writer);
|
||||
|
||||
writer.Flush();
|
||||
writer.Close();
|
||||
}
|
||||
|
||||
protected virtual IDictionary<string, string> GetControlAttributes()
|
||||
{
|
||||
return Attributes;
|
||||
}
|
||||
}
|
||||
48
src/lib/MBS.Web/CssClassList.cs
Normal file
48
src/lib/MBS.Web/CssClassList.cs
Normal file
@ -0,0 +1,48 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public class CssClassList : List<string>
|
||||
{
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the System.Collections.Generic.List`1 class that
|
||||
// is empty and has the default initial capacity.
|
||||
public CssClassList() : base() { }
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the System.Collections.Generic.List`1 class that
|
||||
// contains elements copied from the specified collection and has sufficient capacity
|
||||
// to accommodate the number of elements copied.
|
||||
//
|
||||
// Parameters:
|
||||
// collection:
|
||||
// The collection whose elements are copied to the new list.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentNullException:
|
||||
// collection is null.
|
||||
public CssClassList(IEnumerable<string> collection) : base(collection) { }
|
||||
//
|
||||
// Summary:
|
||||
// Initializes a new instance of the System.Collections.Generic.List`1 class that
|
||||
// is empty and has the specified initial capacity.
|
||||
//
|
||||
// Parameters:
|
||||
// capacity:
|
||||
// The number of elements that the new list can initially store.
|
||||
//
|
||||
// Exceptions:
|
||||
// T:System.ArgumentOutOfRangeException:
|
||||
// capacity is less than 0.
|
||||
public CssClassList(int capacity) : base(capacity) { }
|
||||
|
||||
public new void Add(string value)
|
||||
{
|
||||
string[] vals = value.Split(new char[] { ' ' });
|
||||
foreach (string val in vals)
|
||||
{
|
||||
string v = val.Trim();
|
||||
if (!String.IsNullOrEmpty(v))
|
||||
base.Add(val.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/lib/MBS.Web/Handlers/RedirectWebHandler.cs
Normal file
24
src/lib/MBS.Web/Handlers/RedirectWebHandler.cs
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class RedirectWebHandler : IWebHandler
|
||||
{
|
||||
public string TargetUrl { get; }
|
||||
public RedirectWebHandler(string targetUrl)
|
||||
{
|
||||
TargetUrl = targetUrl;
|
||||
}
|
||||
|
||||
public void ProcessRequest(WebContext context)
|
||||
{
|
||||
context.Response.ResponseCode = 302;
|
||||
context.Response.ResponseText = "Found";
|
||||
|
||||
string actualUrl = TargetUrl.Replace("~/", context.Application.VirtualBasePath);
|
||||
foreach (KeyValuePair<string, string> kvp in context.Request.PathVariables)
|
||||
{
|
||||
actualUrl = actualUrl.Replace("{" + kvp.Key + "}", kvp.Value);
|
||||
}
|
||||
context.Response.Headers.Add("Location", actualUrl);
|
||||
}
|
||||
}
|
||||
6
src/lib/MBS.Web/IWebHandler.cs
Normal file
6
src/lib/MBS.Web/IWebHandler.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public interface IWebHandler
|
||||
{
|
||||
void ProcessRequest(WebContext context);
|
||||
}
|
||||
10
src/lib/MBS.Web/MBS.Web.csproj
Normal file
10
src/lib/MBS.Web/MBS.Web.csproj
Normal file
@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
15
src/lib/MBS.Web/RenderEventArgs.cs
Normal file
15
src/lib/MBS.Web/RenderEventArgs.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public class RenderEventArgs : EventArgs
|
||||
{
|
||||
|
||||
public WebRequest Request { get; }
|
||||
public WebResponse Response { get; }
|
||||
|
||||
public RenderEventArgs(WebRequest request, WebResponse response)
|
||||
{
|
||||
Request = request;
|
||||
Response = response;
|
||||
}
|
||||
|
||||
}
|
||||
46
src/lib/MBS.Web/UI/HtmlControls/HtmlGenericControl.cs
Normal file
46
src/lib/MBS.Web/UI/HtmlControls/HtmlGenericControl.cs
Normal file
@ -0,0 +1,46 @@
|
||||
namespace MBS.Web.UI.HtmlControls;
|
||||
|
||||
using System.Xml;
|
||||
using MBS.Core.Collections.Generic;
|
||||
|
||||
public class HtmlGenericControl : Control
|
||||
{
|
||||
private string _tagName;
|
||||
protected override string TagName => _tagName;
|
||||
|
||||
public string? Content { get; set; } = null;
|
||||
|
||||
public HtmlGenericControl(string tagName)
|
||||
{
|
||||
_tagName = tagName;
|
||||
}
|
||||
public HtmlGenericControl(string tagName, IEnumerable<KeyValuePair<string, string>> attributes, IList<string> classes, string? content = null)
|
||||
{
|
||||
_tagName = tagName;
|
||||
Attributes.AddRange(attributes);
|
||||
ClassList.AddRange(classes);
|
||||
Content = content;
|
||||
}
|
||||
public HtmlGenericControl(string tagName, IEnumerable<KeyValuePair<string, string>> attributes, string? content = null)
|
||||
{
|
||||
_tagName = tagName;
|
||||
Attributes.AddRange(attributes);
|
||||
Content = content;
|
||||
}
|
||||
public HtmlGenericControl(string tagName, IEnumerable<string> classes, string? content = null)
|
||||
{
|
||||
_tagName = tagName;
|
||||
ClassList.AddRange(classes);
|
||||
Content = content;
|
||||
}
|
||||
|
||||
protected override void RenderContents(XmlWriter writer)
|
||||
{
|
||||
base.RenderContents(writer);
|
||||
if (Content != null)
|
||||
{
|
||||
writer.WriteRaw(Content);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
28
src/lib/MBS.Web/UI/HtmlControls/HtmlLink.cs
Normal file
28
src/lib/MBS.Web/UI/HtmlControls/HtmlLink.cs
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
namespace MBS.Web.UI.HtmlControls;
|
||||
|
||||
public class HtmlLink : Control
|
||||
{
|
||||
|
||||
public string Rel { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
public string TargetUrl { get; set; }
|
||||
|
||||
public HtmlLink(string rel, string contentType, string targetUrl)
|
||||
{
|
||||
Rel = rel;
|
||||
ContentType = contentType;
|
||||
TargetUrl = targetUrl;
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetControlAttributes()
|
||||
{
|
||||
IDictionary<string, string> list = base.GetControlAttributes();
|
||||
list.Add("rel", Rel);
|
||||
list.Add("type", ContentType);
|
||||
list.Add("href", TargetUrl);
|
||||
return list;
|
||||
}
|
||||
protected override string TagName => "link";
|
||||
|
||||
}
|
||||
28
src/lib/MBS.Web/UI/WebControl.cs
Normal file
28
src/lib/MBS.Web/UI/WebControl.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Xml;
|
||||
using MBS.Core;
|
||||
|
||||
namespace MBS.Web.UI;
|
||||
|
||||
public abstract class WebControl : Control
|
||||
{
|
||||
private NanoId _NanoId = NanoId.Empty;
|
||||
private string NanoIdString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_NanoId.IsEmpty)
|
||||
{
|
||||
_NanoId = NanoId.Generate(NanoId.CapitalAlphanumericNoSpecialChars, 8);
|
||||
}
|
||||
return _NanoId.ToString();
|
||||
}
|
||||
}
|
||||
public string ClientId { get { return String.Format("UWT{0}", NanoIdString); } }
|
||||
|
||||
protected override IDictionary<string, string> GetControlAttributes()
|
||||
{
|
||||
IDictionary<string, string> dict = base.GetControlAttributes();
|
||||
dict["id"] = ClientId;
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
56
src/lib/MBS.Web/UI/WebControlWithMnemonic.cs
Normal file
56
src/lib/MBS.Web/UI/WebControlWithMnemonic.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace MBS.Web.UI;
|
||||
|
||||
public abstract class WebControlWithMnemonic : WebControl
|
||||
{
|
||||
public WebControlWithMnemonic(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public string Text { get; set; }
|
||||
public bool UseMnemonic { get; set; } = true;
|
||||
public bool HasMnemonic { get { return MnemonicChar != '\0'; } }
|
||||
public char MnemonicChar
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] textForMnemonic = Text.Split(new char[] { '_' }, 2);
|
||||
if (textForMnemonic.Length > 1)
|
||||
{
|
||||
return textForMnemonic[1][0];
|
||||
}
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected string GetTextWithoutMnemonic()
|
||||
{
|
||||
if (UseMnemonic)
|
||||
{
|
||||
return Text.Replace("_", String.Empty);
|
||||
}
|
||||
return Text;
|
||||
}
|
||||
protected void WriteTextWithMnemonic(XmlWriter writer)
|
||||
{
|
||||
if (UseMnemonic)
|
||||
{
|
||||
string[] textForMnemonic = Text.Split(new char[] { '_' }, 2);
|
||||
if (textForMnemonic.Length > 1)
|
||||
{
|
||||
// Blah blah the n_ext underline
|
||||
writer.WriteRaw(textForMnemonic[0]);
|
||||
writer.WriteStartElement("u");
|
||||
writer.WriteRaw(textForMnemonic[1].Substring(0, 1));
|
||||
writer.WriteEndElement();
|
||||
writer.WriteRaw(textForMnemonic[1].Substring(1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
writer.WriteRaw(Text);
|
||||
}
|
||||
|
||||
}
|
||||
41
src/lib/MBS.Web/UI/WebControls/Button.cs
Normal file
41
src/lib/MBS.Web/UI/WebControls/Button.cs
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
using System.Xml;
|
||||
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public class Button : WebControlWithMnemonic
|
||||
{
|
||||
public bool UseSubmitBehavior { get; set; } = false;
|
||||
|
||||
protected override string TagName => "button"; // UseSubmitBehavior ? "input" : "button";
|
||||
|
||||
public Button(string text) : base(text) { }
|
||||
|
||||
protected override void RenderContents(XmlWriter writer)
|
||||
{
|
||||
// if (!UseSubmitBehavior)
|
||||
{
|
||||
WriteTextWithMnemonic(writer);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetControlAttributes()
|
||||
{
|
||||
IDictionary<string, string> attrs = base.GetControlAttributes();
|
||||
if (UseSubmitBehavior)
|
||||
{
|
||||
attrs["type"] = "submit";
|
||||
// attrs["value"] = GetTextWithoutMnemonic();
|
||||
}
|
||||
// else
|
||||
{
|
||||
if (HasMnemonic)
|
||||
{
|
||||
attrs["accesskey"] = MnemonicChar.ToString();
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
src/lib/MBS.Web/UI/WebControls/Container.cs
Normal file
22
src/lib/MBS.Web/UI/WebControls/Container.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public class Container : ContainerBase
|
||||
{
|
||||
protected override string TagName => _tagName;
|
||||
|
||||
private string _tagName = "div";
|
||||
public Container()
|
||||
{
|
||||
}
|
||||
public Container(string tagName)
|
||||
{
|
||||
_tagName = tagName;
|
||||
}
|
||||
|
||||
public Control.ControlCollection Controls { get; } = new Control.ControlCollection();
|
||||
|
||||
protected override IList<Control> GetChildControls()
|
||||
{
|
||||
return Controls;
|
||||
}
|
||||
}
|
||||
28
src/lib/MBS.Web/UI/WebControls/ContainerBase.cs
Normal file
28
src/lib/MBS.Web/UI/WebControls/ContainerBase.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public abstract class ContainerBase : WebControl
|
||||
{
|
||||
private List<Control> _list = new List<Control>();
|
||||
protected virtual IList<Control> GetChildControls()
|
||||
{
|
||||
return _list;
|
||||
}
|
||||
|
||||
private IList<Control>? _childControls = null;
|
||||
|
||||
protected override void RenderContents(XmlWriter writer)
|
||||
{
|
||||
base.RenderContents(writer);
|
||||
|
||||
if (_childControls == null)
|
||||
{
|
||||
_childControls = GetChildControls();
|
||||
}
|
||||
foreach (Control control in _childControls)
|
||||
{
|
||||
control.Render(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/lib/MBS.Web/UI/WebControls/FormView.cs
Normal file
72
src/lib/MBS.Web/UI/WebControls/FormView.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using MBS.Core.Collections.Generic;
|
||||
using MBS.Web.UI.HtmlControls;
|
||||
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public class FormView : ContainerBase
|
||||
{
|
||||
public class FormViewItem : WebControl
|
||||
{
|
||||
public class FormViewItemCollection
|
||||
: System.Collections.ObjectModel.Collection<FormViewItem>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string Title { get; set; }
|
||||
public WebControl Control { get; set; }
|
||||
|
||||
public FormViewItem(string title, WebControl control)
|
||||
{
|
||||
Title = title;
|
||||
Control = control;
|
||||
}
|
||||
}
|
||||
|
||||
public FormViewItem.FormViewItemCollection Items { get; } = new FormViewItem.FormViewItemCollection();
|
||||
|
||||
protected override void InitializeInternal()
|
||||
{
|
||||
base.InitializeInternal();
|
||||
ClassList.Add("uwt-formview");
|
||||
}
|
||||
|
||||
protected override string TagName => "table";
|
||||
|
||||
protected override IList<Control> GetChildControls()
|
||||
{
|
||||
List<Control> list = new List<Control>();
|
||||
foreach (FormViewItem item in Items)
|
||||
{
|
||||
Container tr = new Container("tr");
|
||||
tr.ClassList.Add("uwt-formview-item");
|
||||
|
||||
Label lbl = new Label(item.Title);
|
||||
Container tdLabel = new Container("td");
|
||||
tdLabel.ClassList.Add("uwt-formview-item-label");
|
||||
tdLabel.Controls.Add(lbl);
|
||||
tr.Controls.Add(tdLabel);
|
||||
|
||||
Container tdContent = new Container("td");
|
||||
tdContent.ClassList.Add("uwt-formview-item-content");
|
||||
if (lbl.HasMnemonic)
|
||||
{
|
||||
item.Control.Attributes.Add("accesskey", lbl.MnemonicChar.ToString());
|
||||
}
|
||||
tdContent.Controls.Add(item.Control);
|
||||
tr.Controls.Add(tdContent);
|
||||
|
||||
list.Add(tr);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public FormView(IEnumerable<FormViewItem>? items = null)
|
||||
{
|
||||
if (items != null)
|
||||
{
|
||||
Items.AddRange(items);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
src/lib/MBS.Web/UI/WebControls/Label.cs
Normal file
14
src/lib/MBS.Web/UI/WebControls/Label.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public class Label : WebControlWithMnemonic
|
||||
{
|
||||
protected override string TagName => "label";
|
||||
|
||||
public Label(string text) : base(text) { }
|
||||
protected override void RenderContents(XmlWriter writer)
|
||||
{
|
||||
WriteTextWithMnemonic(writer);
|
||||
}
|
||||
}
|
||||
42
src/lib/MBS.Web/UI/WebControls/Panel.cs
Normal file
42
src/lib/MBS.Web/UI/WebControls/Panel.cs
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
|
||||
using MBS.Core.Collections.Generic;
|
||||
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public class Panel : ContainerBase
|
||||
{
|
||||
protected override void InitializeInternal()
|
||||
{
|
||||
base.InitializeInternal();
|
||||
ClassList.Add("uwt-panel");
|
||||
}
|
||||
|
||||
public List<Control> HeaderControls { get; } = new List<Control>();
|
||||
public List<Control> ContentControls { get; } = new List<Control>();
|
||||
public List<Control> FooterControls { get; } = new List<Control>();
|
||||
|
||||
protected override string TagName => "div";
|
||||
protected override IList<Control> GetChildControls()
|
||||
{
|
||||
List<Control> actual = new List<Control>();
|
||||
|
||||
Container divHeader = new Container();
|
||||
divHeader.ClassList.Add("uwt-header");
|
||||
divHeader.Controls.AddRange(HeaderControls);
|
||||
actual.Add(divHeader);
|
||||
|
||||
Container divContent = new Container();
|
||||
divContent.ClassList.Add("uwt-content");
|
||||
divContent.Controls.AddRange(ContentControls);
|
||||
actual.Add(divContent);
|
||||
|
||||
Container divFooter = new Container();
|
||||
divFooter.ClassList.Add("uwt-footer");
|
||||
divFooter.Controls.AddRange(FooterControls);
|
||||
actual.Add(divFooter);
|
||||
|
||||
return actual;
|
||||
}
|
||||
|
||||
}
|
||||
40
src/lib/MBS.Web/UI/WebControls/TextBox.cs
Normal file
40
src/lib/MBS.Web/UI/WebControls/TextBox.cs
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
namespace MBS.Web.UI.WebControls;
|
||||
|
||||
public enum TextBoxType
|
||||
{
|
||||
None,
|
||||
Password
|
||||
}
|
||||
|
||||
public class TextBox : WebControl
|
||||
{
|
||||
public TextBoxType TextBoxType { get; set; } = TextBoxType.None;
|
||||
|
||||
public TextBox(TextBoxType type = TextBoxType.None)
|
||||
{
|
||||
TextBoxType = type;
|
||||
}
|
||||
|
||||
protected override string TagName => "input";
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
private string GetTextBoxType()
|
||||
{
|
||||
switch (TextBoxType)
|
||||
{
|
||||
case TextBoxType.Password:
|
||||
return "password";
|
||||
}
|
||||
return "text";
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetControlAttributes()
|
||||
{
|
||||
IDictionary<string, string> dict = base.GetControlAttributes();
|
||||
dict["type"] = GetTextBoxType();
|
||||
dict["name"] = Name;
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
72
src/lib/MBS.Web/UI/WebPage.cs
Normal file
72
src/lib/MBS.Web/UI/WebPage.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.Xml;
|
||||
using MBS.Web.UI.HtmlControls;
|
||||
|
||||
namespace MBS.Web.UI;
|
||||
|
||||
public class WebPage : Control
|
||||
{
|
||||
public WebPage(Dictionary<string, string>? pathVariables = null) : base(pathVariables) { }
|
||||
|
||||
public Control.ControlCollection HeaderControls { get; } = new Control.ControlCollection();
|
||||
public Control.ControlCollection Controls { get; } = new Control.ControlCollection();
|
||||
|
||||
public WebStyleSheet.WebStyleSheetCollection StyleSheets { get; } = new WebStyleSheet.WebStyleSheetCollection();
|
||||
|
||||
private bool ChildControlsCreated { get; set; } = false;
|
||||
protected virtual void CreateChildControls()
|
||||
{
|
||||
}
|
||||
|
||||
protected override string TagName => "html";
|
||||
protected override void RenderBeginTag(XmlWriter writer)
|
||||
{
|
||||
EnsureInitialized();
|
||||
writer.WriteStartElement(TagName, "http://www.w3.org/1999/xhtml");
|
||||
}
|
||||
|
||||
protected override void RenderContents(XmlWriter writer)
|
||||
{
|
||||
if (!ChildControlsCreated)
|
||||
{
|
||||
CreateChildControls();
|
||||
ChildControlsCreated = true;
|
||||
}
|
||||
|
||||
writer.WriteStartElement("head");
|
||||
writer.WriteElementString("title", "Mocha Application");
|
||||
|
||||
List<Control> ctls = new List<Control>();
|
||||
ctls.Add(new HtmlLink("stylesheet", "text/css", "/madi/asset/ui-html/2024.27.5/css/mochaApp.css?plate=BMT216A&sha256-XjJJ2%2BcFxZXtxY579nwOKBNYdP1KUySxNDbxR4QGxvQ%3D"));
|
||||
|
||||
foreach (WebStyleSheet ss in StyleSheets)
|
||||
{
|
||||
if (ss.FileName != null)
|
||||
{
|
||||
ctls.Add(new HtmlLink("stylesheet", ss.ContentType, ss.FileName));
|
||||
}
|
||||
else if (ss.Content != null)
|
||||
{
|
||||
ctls.Add(new HtmlGenericControl("style", new KeyValuePair<string, string>[] { new KeyValuePair<string, string>("type", ss.ContentType) }, ss.Content));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Control control in ctls)
|
||||
{
|
||||
control.Render(writer);
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("body");
|
||||
|
||||
writer.WriteStartElement("form");
|
||||
writer.WriteAttributeString("method", "POST");
|
||||
foreach (WebControl control in Controls)
|
||||
{
|
||||
control.Render(writer);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
33
src/lib/MBS.Web/UI/WebStyleSheet.cs
Normal file
33
src/lib/MBS.Web/UI/WebStyleSheet.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace MBS.Web.UI;
|
||||
|
||||
public class WebStyleSheet
|
||||
{
|
||||
public class WebStyleSheetCollection
|
||||
: System.Collections.ObjectModel.Collection<WebStyleSheet>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string ContentType { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
|
||||
private WebStyleSheet(string contentType)
|
||||
{
|
||||
ContentType = contentType;
|
||||
}
|
||||
|
||||
public static WebStyleSheet FromContent(string contentType, string content)
|
||||
{
|
||||
WebStyleSheet styleSheet = new WebStyleSheet(contentType);
|
||||
styleSheet.Content = content;
|
||||
return styleSheet;
|
||||
}
|
||||
|
||||
public static WebStyleSheet FromFile(string contentType, string filename)
|
||||
{
|
||||
WebStyleSheet styleSheet = new WebStyleSheet(contentType);
|
||||
styleSheet.FileName = filename;
|
||||
return styleSheet;
|
||||
}
|
||||
}
|
||||
55
src/lib/MBS.Web/WebApplication.cs
Normal file
55
src/lib/MBS.Web/WebApplication.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using MBS.Core;
|
||||
using MBS.Web.UI;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public abstract class WebApplication : Application
|
||||
{
|
||||
protected abstract int DefaultPort { get; }
|
||||
|
||||
public string VirtualBasePath { get; set; } = "/";
|
||||
|
||||
protected override void OnBeforeStartInternal(CancelEventArgs e)
|
||||
{
|
||||
base.OnBeforeStartInternal(e);
|
||||
|
||||
CommandLine.Options.Add("port", 'p', 10200, CommandLineOptionValueType.Single, "the port on which to listen for HTTP(S) requests");
|
||||
}
|
||||
|
||||
public EventHandler<WebServerProcessRequestEventArgs> ProcessRequest;
|
||||
protected virtual void OnProcessRequest(WebServerProcessRequestEventArgs e)
|
||||
{
|
||||
ProcessRequest?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void server_OnProcessRequest(object sender, WebServerProcessRequestEventArgs e)
|
||||
{
|
||||
OnProcessRequest(e);
|
||||
}
|
||||
|
||||
public event EventHandler<WebServerCreatedEventArgs> ServerCreated;
|
||||
protected virtual void OnServerCreated(WebServerCreatedEventArgs e)
|
||||
{
|
||||
ServerCreated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected override void OnStartup(EventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
Console.WriteLine("starting server...");
|
||||
|
||||
WebServer server = new WebServer(new IPEndPoint(IPAddress.Any, DefaultPort));
|
||||
OnServerCreated(new WebServerCreatedEventArgs(server));
|
||||
server.ProcessRequest += server_OnProcessRequest;
|
||||
server.Start();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
15
src/lib/MBS.Web/WebContext.cs
Normal file
15
src/lib/MBS.Web/WebContext.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebContext
|
||||
{
|
||||
public WebContext(WebApplication application, WebRequest request, WebResponse response)
|
||||
{
|
||||
Application = application;
|
||||
Request = request;
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public WebApplication Application { get; }
|
||||
public WebRequest Request { get; }
|
||||
public WebResponse Response { get; }
|
||||
}
|
||||
92
src/lib/MBS.Web/WebCookie.cs
Normal file
92
src/lib/MBS.Web/WebCookie.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System.Text;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebCookie
|
||||
{
|
||||
public class WebCookieCollection
|
||||
: System.Collections.ObjectModel.Collection<WebCookie>
|
||||
{
|
||||
private Dictionary<string, WebCookie> _list = new Dictionary<string, WebCookie>();
|
||||
|
||||
public void Add(string key, string value, WebCookieScope scope, WebCookieSecurity security, WebCookieSameSite sameSite)
|
||||
{
|
||||
WebCookie cookie = new WebCookie();
|
||||
cookie.key = key;
|
||||
cookie.value = value;
|
||||
cookie.scope = scope;
|
||||
cookie.security = security;
|
||||
cookie.sameSite = sameSite;
|
||||
_list[key] = cookie;
|
||||
base.Add(cookie);
|
||||
}
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_list.ContainsKey(key))
|
||||
{
|
||||
return _list[key].value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string key;
|
||||
public string value;
|
||||
public WebCookieScope scope;
|
||||
public WebCookieSecurity security;
|
||||
public WebCookieSameSite sameSite;
|
||||
|
||||
|
||||
public string GetCookieString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(key);
|
||||
sb.Append("=");
|
||||
sb.Append(value);
|
||||
if (!scope.IsEmpty)
|
||||
{
|
||||
sb.Append("; ");
|
||||
if (scope.path != null)
|
||||
{
|
||||
sb.Append("Path=");
|
||||
sb.Append(scope.path);
|
||||
}
|
||||
}
|
||||
if (security != WebCookieSecurity.None)
|
||||
{
|
||||
sb.Append("; ");
|
||||
List<string> parms = new List<string>();
|
||||
if ((security & WebCookieSecurity.Secure) == WebCookieSecurity.Secure)
|
||||
{
|
||||
parms.Add("Secure");
|
||||
}
|
||||
if ((security & WebCookieSecurity.HttpOnly) == WebCookieSecurity.HttpOnly)
|
||||
{
|
||||
parms.Add("HttpOnly");
|
||||
}
|
||||
sb.Append(String.Join("; ", parms));
|
||||
}
|
||||
if (sameSite != WebCookieSameSite.Lax)
|
||||
{
|
||||
sb.Append("; SameSite=");
|
||||
switch (sameSite)
|
||||
{
|
||||
case WebCookieSameSite.None:
|
||||
{
|
||||
sb.Append("None");
|
||||
break;
|
||||
}
|
||||
case WebCookieSameSite.Strict:
|
||||
{
|
||||
sb.Append("Strict");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
8
src/lib/MBS.Web/WebCookieSameSite.cs
Normal file
8
src/lib/MBS.Web/WebCookieSameSite.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public enum WebCookieSameSite
|
||||
{
|
||||
None = 0,
|
||||
Strict = 1,
|
||||
Lax = 2
|
||||
}
|
||||
28
src/lib/MBS.Web/WebCookieScope.cs
Normal file
28
src/lib/MBS.Web/WebCookieScope.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public struct WebCookieScope
|
||||
{
|
||||
public string? domain;
|
||||
public string? path;
|
||||
private bool _isNotEmpty;
|
||||
public bool IsEmpty { get { return !_isNotEmpty; } }
|
||||
|
||||
public static WebCookieScope FromDomain(string domain)
|
||||
{
|
||||
WebCookieScope scope = new WebCookieScope();
|
||||
scope.domain = domain;
|
||||
scope.path = null;
|
||||
scope._isNotEmpty = true;
|
||||
return scope;
|
||||
}
|
||||
public static WebCookieScope FromPath(string path)
|
||||
{
|
||||
WebCookieScope scope = new WebCookieScope();
|
||||
scope.domain = null;
|
||||
scope.path = path;
|
||||
scope._isNotEmpty = true;
|
||||
return scope;
|
||||
}
|
||||
|
||||
public static WebCookieScope Empty;
|
||||
}
|
||||
9
src/lib/MBS.Web/WebCookieSecurity.cs
Normal file
9
src/lib/MBS.Web/WebCookieSecurity.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
[Flags()]
|
||||
public enum WebCookieSecurity
|
||||
{
|
||||
None = 0,
|
||||
Secure = 1,
|
||||
HttpOnly = 2
|
||||
}
|
||||
27
src/lib/MBS.Web/WebHandler.cs
Normal file
27
src/lib/MBS.Web/WebHandler.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebHandler : IWebHandler
|
||||
{
|
||||
public Action<WebContext>? Action { get; }
|
||||
public WebHandler() : this(null) { }
|
||||
public WebHandler(Action<WebContext>? action)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public void ProcessRequest(WebContext context)
|
||||
{
|
||||
ProcessRequestInternal(context);
|
||||
}
|
||||
|
||||
protected virtual void ProcessRequestInternal(WebContext context)
|
||||
{
|
||||
if (Action == null)
|
||||
{
|
||||
context.Response.ResponseCode = 404;
|
||||
context.Response.ResponseText = "Not Found";
|
||||
return;
|
||||
}
|
||||
Action(context);
|
||||
}
|
||||
}
|
||||
85
src/lib/MBS.Web/WebHeaderCollection.cs
Normal file
85
src/lib/MBS.Web/WebHeaderCollection.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System.Net.Http.Headers;
|
||||
using MBS.Core;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebHeaderCollection : List<KeyValuePair<string, string>>
|
||||
{
|
||||
private Dictionary<string, List<string>> _list = new Dictionary<string, List<string>>();
|
||||
public string? this[System.Net.HttpRequestHeader key]
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case System.Net.HttpRequestHeader.Accept: return this["Accept"];
|
||||
case System.Net.HttpRequestHeader.AcceptCharset: return this["Accept-Charset"];
|
||||
case System.Net.HttpRequestHeader.AcceptEncoding: return this["Accept-Encoding"];
|
||||
case System.Net.HttpRequestHeader.AcceptLanguage: return this["Accept-Language"];
|
||||
case System.Net.HttpRequestHeader.Allow: return this["Allow"];
|
||||
case System.Net.HttpRequestHeader.Authorization: return this["Authorization"];
|
||||
case System.Net.HttpRequestHeader.CacheControl: return this["Cache-Control"];
|
||||
case System.Net.HttpRequestHeader.Connection: return this["Connection"];
|
||||
case System.Net.HttpRequestHeader.ContentEncoding: return this["Content-Encoding"];
|
||||
case System.Net.HttpRequestHeader.ContentLanguage: return this["Content-Language"];
|
||||
case System.Net.HttpRequestHeader.ContentLength: return this["Content-Length"];
|
||||
case System.Net.HttpRequestHeader.ContentLocation: return this["Content-Location"];
|
||||
case System.Net.HttpRequestHeader.ContentMd5: return this["Content-MD5"];
|
||||
case System.Net.HttpRequestHeader.ContentRange: return this["Content-Range"];
|
||||
case System.Net.HttpRequestHeader.ContentType: return this["Content-Type"];
|
||||
case System.Net.HttpRequestHeader.Cookie: return this["Cookie"];
|
||||
case System.Net.HttpRequestHeader.Date: return this["Date"];
|
||||
case System.Net.HttpRequestHeader.Expect: return this["Expect"];
|
||||
}
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public string? this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_list.ContainsKey(key))
|
||||
{
|
||||
if (_list[key].Count > 0)
|
||||
{
|
||||
return _list[key][_list[key].Count - 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!_list.ContainsKey(key))
|
||||
{
|
||||
_list[key] = new List<string>();
|
||||
}
|
||||
if (!_list[key].Contains(value))
|
||||
{
|
||||
_list[key].Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetValues(string key)
|
||||
{
|
||||
return _list[key];
|
||||
}
|
||||
|
||||
public void Add(string key, string value)
|
||||
{
|
||||
Add(new KeyValuePair<string, string>(key, value));
|
||||
}
|
||||
public new void Add(KeyValuePair<string, string> value)
|
||||
{
|
||||
if (!_list.ContainsKey(value.Key))
|
||||
{
|
||||
_list[value.Key] = new List<string>();
|
||||
}
|
||||
if (!_list[value.Key].Contains(value.Value))
|
||||
{
|
||||
_list[value.Key].Add(value.Value);
|
||||
}
|
||||
base.Add(value);
|
||||
}
|
||||
}
|
||||
28
src/lib/MBS.Web/WebRequest.cs
Normal file
28
src/lib/MBS.Web/WebRequest.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Net;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebRequest
|
||||
{
|
||||
public string Version { get; }
|
||||
public string Method { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public Uri? Uri { get; } = null;
|
||||
|
||||
public Dictionary<string, string> PathVariables { get; }
|
||||
public WebHeaderCollection Headers { get; }
|
||||
|
||||
public WebRequest(string version, string method, string path, WebHeaderCollection headers, Dictionary<string, string> pathVariables)
|
||||
{
|
||||
Version = version;
|
||||
Method = method;
|
||||
Path = path;
|
||||
if (Uri.TryCreate(path, UriKind.Relative, out Uri? uri))
|
||||
{
|
||||
Uri = uri;
|
||||
}
|
||||
Headers = headers;
|
||||
PathVariables = pathVariables;
|
||||
}
|
||||
}
|
||||
23
src/lib/MBS.Web/WebResponse.cs
Normal file
23
src/lib/MBS.Web/WebResponse.cs
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
using MBS.Core;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebResponse
|
||||
{
|
||||
public int ResponseCode { get; set; } = 200;
|
||||
public string ResponseText { get; set; } = "OK";
|
||||
|
||||
public WebHeaderCollection Headers { get; } = new WebHeaderCollection();
|
||||
public WebCookie.WebCookieCollection Cookies { get; } = new WebCookie.WebCookieCollection();
|
||||
|
||||
public MemoryStream Stream { get; } = new MemoryStream();
|
||||
public string? ContentType { get; set; } = null;
|
||||
|
||||
public void Redirect(string path)
|
||||
{
|
||||
ResponseCode = 302;
|
||||
ResponseText = "Found";
|
||||
Headers.Add("Location", path.Replace("~/", ((WebApplication)Application.Instance).VirtualBasePath));
|
||||
}
|
||||
}
|
||||
142
src/lib/MBS.Web/WebRoute.cs
Normal file
142
src/lib/MBS.Web/WebRoute.cs
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebRoute
|
||||
{
|
||||
public class WebRouteCollection
|
||||
: System.Collections.ObjectModel.Collection<WebRoute>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string PathTemplate { get; }
|
||||
public IWebHandler Handler { get; }
|
||||
|
||||
public WebRoute(string pathTemplate, IWebHandler handler)
|
||||
{
|
||||
PathTemplate = pathTemplate;
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
public bool Matches(string pathstr, Dictionary<string, string> variables)
|
||||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
bool escape = false;
|
||||
bool insideVariable = false;
|
||||
|
||||
string? varname = null, varvalue = null;
|
||||
|
||||
while (i < PathTemplate.Length)
|
||||
{
|
||||
if (PathTemplate[i] == '\\')
|
||||
{
|
||||
escape = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!escape && (PathTemplate[i] == '{' || (PathTemplate[i] == '$' && PathTemplate.Length - i > 0 && PathTemplate[i + 1] == '(')))
|
||||
{
|
||||
insideVariable = true;
|
||||
varname = "";
|
||||
varvalue = "";
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else if (!escape && (insideVariable && (PathTemplate[i] == '}' || PathTemplate[i] == ')')))
|
||||
{
|
||||
insideVariable = false;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insideVariable)
|
||||
{
|
||||
varname += PathTemplate[i];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PathTemplate[i] == pathstr[j])
|
||||
{
|
||||
// yay, we match
|
||||
// save the last-known variable, if any...
|
||||
if (varname != null && varvalue != null)
|
||||
{
|
||||
variables[varname] = varvalue;
|
||||
|
||||
// don't forget to reset it
|
||||
varname = null;
|
||||
varvalue = null;
|
||||
}
|
||||
|
||||
// ... and keep going
|
||||
i++;
|
||||
j++;
|
||||
|
||||
if (i > PathTemplate.Length - 1 || j > pathstr.Length - 1)
|
||||
{
|
||||
if (i > PathTemplate.Length - 1 && j > pathstr.Length - 1)
|
||||
{
|
||||
// full match with no path vars
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no match
|
||||
if (varname != null)
|
||||
{
|
||||
if (j + 1 < pathstr.Length)
|
||||
{
|
||||
// we are currently reading a variable value
|
||||
varvalue += pathstr[j];
|
||||
j++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// don't even question it, just return false since we should never reach this point
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we do not match!
|
||||
return false;
|
||||
}
|
||||
|
||||
if (varvalue != null)
|
||||
{
|
||||
// we are in a variable
|
||||
varvalue += pathstr[j];
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
escape = false;
|
||||
}
|
||||
|
||||
if (varvalue != null && j < pathstr.Length)
|
||||
{
|
||||
while (j < pathstr.Length)
|
||||
{
|
||||
varvalue += pathstr[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
variables[varname] = varvalue;
|
||||
varname = null;
|
||||
varvalue = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
261
src/lib/MBS.Web/WebServer.cs
Normal file
261
src/lib/MBS.Web/WebServer.cs
Normal file
@ -0,0 +1,261 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using MBS.Core;
|
||||
using MBS.Web.UI;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebServer
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Dictionary" /> of key/value pairs to send along with every request.
|
||||
/// </summary>
|
||||
/// <typeparam name="string"></typeparam>
|
||||
/// <typeparam name="string"></typeparam>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();
|
||||
|
||||
public Dictionary<string, WebSession> Sessions { get; } = new Dictionary<string, WebSession>();
|
||||
|
||||
public string? UserAgent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Headers.ContainsKey("Server"))
|
||||
return Headers["Server"];
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Headers["Server"] = value;
|
||||
}
|
||||
else if (Headers.ContainsKey("Server"))
|
||||
{
|
||||
Headers.Remove("Server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public System.Net.IPEndPoint EndPoint { get; }
|
||||
|
||||
public WebServer(System.Net.IPEndPoint endpoint)
|
||||
{
|
||||
EndPoint = endpoint;
|
||||
}
|
||||
|
||||
public WebRoute.WebRouteCollection Routes { get; } = new WebRoute.WebRouteCollection();
|
||||
|
||||
public event EventHandler<WebServerProcessRequestEventArgs> ProcessRequest;
|
||||
protected virtual void OnProcessRequest(WebServerProcessRequestEventArgs e)
|
||||
{
|
||||
ProcessRequest?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void tServer_ThreadStart()
|
||||
{
|
||||
System.Net.Sockets.TcpListener listener = new System.Net.Sockets.TcpListener(EndPoint);
|
||||
listener.Start();
|
||||
|
||||
while (!_Stopping)
|
||||
{
|
||||
System.Net.Sockets.TcpClient client = listener.AcceptTcpClient();
|
||||
|
||||
Thread tClientThread = new Thread(tClientThread_ParameterizedThreadStart);
|
||||
tClientThread.Start(client);
|
||||
}
|
||||
}
|
||||
// JSESSIONID=0068F5AD24A89AB4AFCC4057F619EADF.authgwy-prod-mzzygbiy.prod-ui-auth.pr502.cust.pdx.wd; Path=/; Secure; HttpOnly; SameSite=None
|
||||
|
||||
private void WriteResponse(WebContext context, Stream stream)
|
||||
{
|
||||
StreamWriter sw = new StreamWriter(stream);
|
||||
sw.WriteLine(String.Format("HTTP/1.1 {0} {1}", context.Response.ResponseCode, context.Response.ResponseText));
|
||||
|
||||
if (context.Response.ContentType != null)
|
||||
{
|
||||
sw.WriteLine(String.Format("Content-Type: {0}", context.Response.ContentType));
|
||||
}
|
||||
else if (context.Response.Headers[HttpRequestHeader.ContentType] != null)
|
||||
{
|
||||
sw.WriteLine(String.Format("Content-Type: {0}", context.Response.Headers[HttpRequestHeader.ContentType]));
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> header in Headers)
|
||||
{
|
||||
sw.WriteLine(String.Format("{0}: {1}", header.Key, header.Value));
|
||||
}
|
||||
foreach (KeyValuePair<string, string> header in context.Response.Headers)
|
||||
{
|
||||
sw.WriteLine(String.Format("{0}: {1}", header.Key, header.Value));
|
||||
}
|
||||
foreach (WebCookie cookie in context.Response.Cookies)
|
||||
{
|
||||
sw.WriteLine(String.Format("Set-Cookie: {0}", cookie.GetCookieString()));
|
||||
}
|
||||
sw.WriteLine();
|
||||
sw.Flush();
|
||||
|
||||
byte[] data = context.Response.Stream.ToArray();
|
||||
Console.WriteLine("response data stream length: {0} bytes", data.Length);
|
||||
|
||||
stream.Write(data, 0, data.Length);
|
||||
// Console.Write(System.Text.Encoding.UTF8.GetString(data));
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
private string CoalesceVariables(string input)
|
||||
{
|
||||
bool insideVariable = false;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] == '{')
|
||||
{
|
||||
insideVariable = true;
|
||||
}
|
||||
else if (input[i] == '}')
|
||||
{
|
||||
insideVariable = false;
|
||||
sb.Append("?");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(input[i]);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void HandleClient(System.Net.Sockets.TcpClient client)
|
||||
{
|
||||
// WebServerProcessRequestEventArgs e = new WebServerProcessRequestEventArgs();
|
||||
// OnClientConnected(e);
|
||||
/*
|
||||
if (e.Cancel)
|
||||
{
|
||||
client.Close();
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
StreamReader sr = new StreamReader(client.GetStream());
|
||||
string line = sr.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
Console.Error.WriteLine("unexpected NULL line from client");
|
||||
client.Close();
|
||||
return;
|
||||
}
|
||||
string[] lineParts = line.Split(new char[] { ' ' });
|
||||
if (lineParts.Length != 3)
|
||||
{
|
||||
Console.Error.WriteLine("unexpected request from client:\n----> {0}", line);
|
||||
client.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
string requestMethod = lineParts[0];
|
||||
string path = lineParts[1];
|
||||
string version = lineParts[2];
|
||||
|
||||
WebHeaderCollection headers = new WebHeaderCollection();
|
||||
while (true)
|
||||
{
|
||||
string headerLine = sr.ReadLine();
|
||||
if (String.IsNullOrEmpty(headerLine))
|
||||
break;
|
||||
|
||||
string[] headerParts = headerLine.Split(new char[] { ':' }, 2);
|
||||
if (headerParts.Length != 2)
|
||||
continue;
|
||||
|
||||
headers[headerParts[0]] = headerParts[1];
|
||||
}
|
||||
|
||||
if (headers[HttpRequestHeader.Cookie] != null)
|
||||
{
|
||||
string cookie = headers[HttpRequestHeader.Cookie];
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
Dictionary<string, string> pathVariables = new Dictionary<string, string>();
|
||||
WebContext context = new WebContext((WebApplication)Application.Instance, new WebRequest(version, requestMethod, path, headers, pathVariables), new WebResponse());
|
||||
context.Response.Cookies.Add("JSESSIONID", "0068F5AD24A89AB4AFCC4057F619EADF.authgwy-prod-mzzygbiy.prod-ui-auth.pr502.cust.pdx.wd", WebCookieScope.FromPath("/"), WebCookieSecurity.Secure | WebCookieSecurity.HttpOnly, WebCookieSameSite.None);
|
||||
|
||||
WebServerProcessRequestEventArgs e = new WebServerProcessRequestEventArgs(client, context);
|
||||
OnProcessRequest(e);
|
||||
if (e.Handled)
|
||||
{
|
||||
WriteResponse(context, client.GetStream());
|
||||
client.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
List<WebRoute> sortedRoutes = new List<WebRoute>(Routes);
|
||||
sortedRoutes.Sort(new Comparison<WebRoute>((left, right) => CoalesceVariables(right.PathTemplate).Length.CompareTo(CoalesceVariables(left.PathTemplate).Length)));
|
||||
foreach (WebRoute route in sortedRoutes)
|
||||
{
|
||||
// !!! FIXME !!! /super/d/~/super/d/login.htmld/... falsely maps to ~/{tenant} route
|
||||
// where {tenant} is super/d/~/super/d/login.htmld ...... ???
|
||||
// which... technically I guess it *should*, but... how do we get it to do what we WANT?
|
||||
|
||||
if (route.Matches(path, pathVariables))
|
||||
{
|
||||
route.Handler.ProcessRequest(context);
|
||||
|
||||
WriteResponse(context, client.GetStream());
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
context.Response.ResponseCode = 404;
|
||||
context.Response.ResponseText = "Not Found";
|
||||
WriteResponse(context, client.GetStream());
|
||||
}
|
||||
|
||||
client.Close();
|
||||
}
|
||||
private void tClientThread_ParameterizedThreadStart(object parm)
|
||||
{
|
||||
System.Net.Sockets.TcpClient client = (System.Net.Sockets.TcpClient)parm;
|
||||
Console.WriteLine("Client connected");
|
||||
|
||||
try
|
||||
{
|
||||
HandleClient(client);
|
||||
}
|
||||
catch (System.Net.Sockets.SocketException ex)
|
||||
{
|
||||
Console.Error.WriteLine("caught SocketException; ignoring");
|
||||
}
|
||||
catch (System.IO.IOException ex)
|
||||
{
|
||||
Console.Error.WriteLine("caught IOException; ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _Stopping = false;
|
||||
private Thread? tServer = null;
|
||||
public void Start()
|
||||
{
|
||||
if (tServer != null)
|
||||
throw new InvalidOperationException("Server already started; please call Stop() first");
|
||||
|
||||
_Stopping = false;
|
||||
|
||||
tServer = new Thread(tServer_ThreadStart);
|
||||
tServer.Start();
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
if (tServer == null)
|
||||
throw new InvalidOperationException("Server not started; please call Start() first");
|
||||
|
||||
_Stopping = true;
|
||||
}
|
||||
}
|
||||
16
src/lib/MBS.Web/WebServerCreatedEventArgs.cs
Normal file
16
src/lib/MBS.Web/WebServerCreatedEventArgs.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using MBS.Core;
|
||||
using MBS.Web.UI;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebServerCreatedEventArgs : EventArgs
|
||||
{
|
||||
public WebServer Server { get; }
|
||||
public WebServerCreatedEventArgs(WebServer server)
|
||||
{
|
||||
Server = server;
|
||||
}
|
||||
|
||||
}
|
||||
18
src/lib/MBS.Web/WebServerProcessRequestEventArgs.cs
Normal file
18
src/lib/MBS.Web/WebServerProcessRequestEventArgs.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using MBS.Core;
|
||||
using MBS.Web.UI;
|
||||
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebServerProcessRequestEventArgs : EventArgs
|
||||
{
|
||||
public WebContext Context { get; }
|
||||
public bool Handled { get; set; } = false;
|
||||
|
||||
public WebServerProcessRequestEventArgs(System.Net.Sockets.TcpClient client, WebContext context)
|
||||
{
|
||||
Context = context;
|
||||
}
|
||||
|
||||
}
|
||||
7
src/lib/MBS.Web/WebSession.cs
Normal file
7
src/lib/MBS.Web/WebSession.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace MBS.Web;
|
||||
|
||||
public class WebSession
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user