Compare commits

...

13 Commits

Author SHA1 Message Date
Harald Christian Joachim Wolff 475fe6e4da Work in Progress 2018-10-17 21:05:00 +02:00
Harald Christian Joachim Wolff 06a3283d40 Work in Progress 2018-10-17 21:02:45 +02:00
Harald Wolff 8d1ea04d57 Work in Progress 2018-09-20 10:51:04 +02:00
Harald Wolff ec3ec514de Request->HttpRequest, ReflectiveResource, etc. 2018-09-07 16:58:42 +02:00
Harald Wolff 89ed4b3418 Work in Progress 2018-07-30 23:21:56 +02:00
Harald Christian Joachim Wolff 2d5d20d3a5 WIP 2018-07-30 09:23:00 +02:00
Harald Wolff 9832676f27 WIP 2018-07-27 13:09:03 +02:00
Harald Wolff a824a500d0 WIP 2018-07-26 16:55:17 +02:00
Harald Wolff 4bac649872 WIP 2018-07-26 00:05:40 +02:00
Harald Wolff 96430f92c0 WIP 2018-07-25 16:43:03 +02:00
Harald Wolff a53341ba60 +TextFormElement 2018-07-24 00:34:48 +02:00
Harald Wolff 34edfcfcbc WIP 2018-07-23 17:12:15 +02:00
Harald Wolff 37462c7e20 WIP 2018-07-23 12:48:06 +02:00
85 changed files with 4741 additions and 562 deletions

View File

@ -5,6 +5,7 @@ using System.Threading;
using appsrv.resources;
using System.IO;
using appsrv.test;
using System.Reflection;
namespace appsrv
{
@ -13,18 +14,20 @@ namespace appsrv
public static void Main(string[] args)
{
ApplicationServer server = new ApplicationServer();
//Application application = new Application(server, "../../../sharp-wawi/bin/Debug/WaWi.xml");
Application application = new Application(server, "../../../netmanager/bin/Debug/netmanager.xml");
//Application application = new Application(server, "test/TestApplication.xml");
Resource root = new DirectoryResource(new DirectoryInfo("./www"));
server.AddRoot("localhost",root);
StaticClassResource staticClassResource = new StaticClassResource(typeof(StaticTest),root);
Http http = new Http(server);
server.Add(application);
Http http = new Http(server);
http.Start();
Thread.Sleep(10000);
bool exit = false;
while (!exit){
Thread.Sleep(1000);
};
http.Stop();

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{FD508FE5-5879-4C60-91D8-CA408E06361F}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>appsrv</RootNamespace>
<AssemblyName>appsrv</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="connector\Http.cs" />
<Compile Include="server\ApplicationServer.cs" />
<Compile Include="connector\Connector.cs" />
<Compile Include="server\Application.cs" />
<Compile Include="server\HttpRequest.cs" />
<Compile Include="exceptions\IllegalRequestException.cs" />
<Compile Include="resources\Resource.cs" />
<Compile Include="resources\FileResource.cs" />
<Compile Include="resources\DirectoryResource.cs" />
<Compile Include="exceptions\ApplicationServerException.cs" />
<Compile Include="exceptions\ResourceNotFoundException.cs" />
<Compile Include="mime\MimeHelper.cs" />
<Compile Include="http\HttpStatusCodes.cs" />
<Compile Include="resources\ResourceLink.cs" />
<Compile Include="resources\StaticClassResource.cs" />
<Compile Include="attributes\WebCallable.cs" />
<Compile Include="test\StaticTest.cs" />
<Compile Include="http\QueryStringParameters.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="connector\" />
<Folder Include="server\" />
<Folder Include="exceptions\" />
<Folder Include="resources\" />
<Folder Include="www\" />
<Folder Include="www\gfx\" />
<Folder Include="mime\" />
<Folder Include="http\" />
<Folder Include="attributes\" />
<Folder Include="test\" />
</ItemGroup>
<ItemGroup>
<None Include="www\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<MonoDevelop>
<Properties>
<Policies>
<DotNetNamingPolicy ResourceNamePolicy="FileFormatDefault" DirectoryNamespaceAssociation="PrefixedHierarchical" />
</Policies>
</Properties>
</MonoDevelop>
</ProjectExtensions>
</Project>

View File

@ -0,0 +1,12 @@
using System;
namespace appsrv.attributes
{
public class HTMLEditorAttribute : Attribute
{
public bool Details { get; set; }
public HTMLEditorAttribute()
{
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace appsrv.attributes
{
public class PublishedMemberAttribute : Attribute
{
public PublishedMemberAttribute()
{
}
}
}

View File

@ -1,20 +0,0 @@
using System;
namespace appsrv.attributes
{
public enum Serialization {
PLAIN,
JSON,
XML
}
public class WebCallable : Attribute
{
public String Name { get; set; }
public Serialization Serialization { get; set; } = Serialization.PLAIN;
public WebCallable()
{
}
}
}

View File

@ -0,0 +1,21 @@
using System;
namespace appsrv.attributes
{
public enum SERIALIZATION {
PLAIN,
JSON,
XML
}
public class WebCallableAttribute : Attribute
{
public String Name { get; set; }
public SERIALIZATION Serialization { get; set; } = SERIALIZATION.PLAIN;
public bool MapPathToParameters { get; set; } = false;
public WebCallableAttribute()
{
}
}
}

View File

@ -4,6 +4,8 @@ using System.Net.Sockets;
using appsrv.connector;
using appsrv.server;
using System.Threading;
using appsrv.http;
using System.IO;
namespace appsrv.protocol
{
@ -50,35 +52,43 @@ namespace appsrv.protocol
private void acceptRequests(){
try
{
while (true)
{
TcpClient client = this.tcpListener.AcceptTcpClient();
try
{
HttpRequest request = new HttpRequest(this.ApplicationServer, client);
Console.WriteLine("new request: {0}",request);
Thread t = new Thread(() => request.Handle());
t.Start();
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e);
}
Thread requestThread = new Thread(() => ClientConnection(client));
requestThread.Start();
}
} catch (SocketException e){
Console.WriteLine("Http connector interupted");
Console.WriteLine("Http connector interupted: {0}",e);
}
}
private void ClientConnection(TcpClient tcpClient)
{
try
{
HttpRequest httpRequest = new HttpRequest(this.ApplicationServer, tcpClient);
ApplicationServer.HandleRequest(httpRequest);
HttpResponse httpResponse = httpRequest.HttpResponse;
if (!httpRequest.Session.ID.ToString().Equals(httpRequest.GetRequestCookie("SAS_SID")))
{
httpResponse.SetCookie("SAS_SID", httpRequest.Session.ID.ToString());
}
httpRequest.Session.Touch();
httpResponse.Send();
tcpClient.Close();
}
catch (IOException e)
{
Console.WriteLine("Exception: {0}", e);
}
}
}
}

22
http/HtmlReader.cs 100644
View File

@ -0,0 +1,22 @@
using System;
using System.Xml;
namespace appsrv.http
{
public class HtmlReader : XmlTextReader
{
public override bool CanResolveEntity => false;
public HtmlReader(String sourceFilename)
:base(sourceFilename)
{
EntityHandling = EntityHandling.ExpandCharEntities;
}
public override void ResolveEntity()
{
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Xml;
using System.IO;
namespace appsrv.http
{
public class HtmlResolver : XmlUrlResolver
{
public HtmlResolver()
{
}
public override Uri ResolveUri(Uri baseUri, string relativeUri)
{
return base.ResolveUri(baseUri, relativeUri);
}
public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
{
return base.GetEntity(absoluteUri,role,ofObjectToReturn);
}
}
}

152
http/HttpRequest.cs 100644
View File

@ -0,0 +1,152 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using appsrv.exceptions;
using appsrv.http;
using appsrv.server;
using Newtonsoft.Json;
using appsrv.sessions;
using appsrv.mime;
namespace appsrv.http
{
public class HttpRequest : Request
{
private Stream stream;
public HttpResponse HttpResponse { get; private set; }
public MIMEMessage MIME { get; protected set; }
public override Response Response => HttpResponse;
private Dictionary<string, string> requestCookies = new Dictionary<string, string>();
public EndPoint LocalEndpoint { get; private set; }
public EndPoint Client { get; private set; }
public String Method { get; private set; }
public String RequestURL { get; private set; }
public String QueryString { get; protected set; }
public String Protocol { get; private set; }
public String Hostname { get; private set; }
public int Port { get; private set; }
public override string ROOT => String.Format("http://{0}:{1}", Hostname, Port);
public QueryStringParameters Query { get; private set; }
public HttpRequest(ApplicationServer applicationServer, TcpClient client)
:base(applicationServer)
{
this.LocalEndpoint = client.Client.LocalEndPoint;
this.Client = client.Client.RemoteEndPoint;
this.stream = client.GetStream();
String rLine = this.stream.ReadLine(Encoding.ASCII);
if (rLine == null)
throw new IOException("connection lost");
String[] rTokens = rLine.SplitWhitespace();
if (rTokens.Length != 3){
throw new IllegalRequestException(rLine);
}
this.Method = rTokens[0];
this.RequestURL = rTokens[1];
this.Protocol = rTokens[2];
MIME = new MIMEMessage(stream, true);
DecodeRequest();
HttpResponse = new HttpResponse(this,stream);
}
private void DecodeRequest()
{
String[] host = MIME.Headers["Host"].Value.Split(':');
if (LocalEndpoint is IPEndPoint)
{
IPEndPoint iPEndPoint = (IPEndPoint)LocalEndpoint;
Port = iPEndPoint.Port;
} else if (host.Length > 1){
Port = int.Parse(host[1]);
}
Hostname = host[0];
Path = RequestURL.ToMark('?');
QueryString = RequestURL.FromMark('?');
Query = new QueryStringParameters(QueryString);
foreach (MimeHeader header in MIME.Headers.GetHeadersByName("COOKIE"))
{
string[] tokens = header.Value.Split(new char[] { '=' }, 2);
if (tokens.Length == 2)
{
requestCookies.Add(tokens[0], tokens[1]);
}
else
{
requestCookies.Add(tokens[0], "");
}
}
Application = ApplicationServer.ApplicationByAlias(Hostname);
if (GetRequestCookie("SAS_SID") != null)
{
try
{
ApplySession(Guid.Parse(GetRequestCookie("SAS_SID")));
} catch (FormatException fe)
{
Console.WriteLine("Invalid SAS_SID: {0}",fe);
}
}
}
public override MIMEMessage GetParameter(string name)
{
foreach (MIMEMessage part in MIME.Parts)
{
if (part.Headers.Contains("content-disposition") &&
part.Headers.GetHeaderByName("content-disposition").ContainsParameter("NAME") &&
name.Equals(part.Headers.GetHeaderByName("content-disposition").GetParameter("NAME"))
)
{
return part;
}
}
return Query[name];
}
public override string ToString()
{
return string.Format("[HttpRequest: Client={0}, ApplicationServer={1}, Hostname={6} Port={7}, Method={3}, RequestURL={4}, Protocol={5} Query={8}]", Client, ApplicationServer, "", Method, RequestURL, Protocol, Hostname, Port,Query);
}
public String GetRequestCookie(String name)
{
if (!requestCookies.ContainsKey(name))
return null;
return requestCookies[name];
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using appsrv.server;
using System.IO;
using appsrv.mime;
using System.Collections.Generic;
namespace appsrv.http
{
public class HttpResponse : Response
{
Stream clientStream;
public HttpRequest HttpRequest { get; protected set; }
Dictionary<string, string> cookies = new Dictionary<string, string>();
public HttpResponse(HttpRequest request,Stream clientStream)
:base(request)
{
this.clientStream = clientStream;
HttpRequest = request;
}
public void SetCookie(string name,string value)
{
cookies.Add(name, value);
}
public void Send()
{
Flush();
using (StreamWriter streamWriter = new StreamWriter(clientStream))
{
byte[] content = ContentBytes;
Headers.Set("Content-Length", content.Length.ToString());
streamWriter.WriteLine("{0} {1} {2}", HttpRequest.Protocol, StatusCode, HttpStatusCodes.GetStatusMessage(StatusCode));
foreach (MimeHeader header in Headers)
{
streamWriter.WriteLine(header.ToString());
}
foreach (string cn in cookies.Keys)
{
streamWriter.WriteLine("Set-Cookie: {0}={1}; Path=/", cn, cookies[cn]);
}
streamWriter.WriteLine();
streamWriter.Flush();
clientStream.Write(content, 0, content.Length);
clientStream.Flush();
}
}
}
}

View File

@ -3,15 +3,16 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using appsrv.mime;
namespace appsrv.http
{
public class QueryStringParameters : IDictionary<String,String>
public class QueryStringParameters : IEnumerable<MIMEMessage>
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
List<MIMEMessage> MIMEMessages = new List<MIMEMessage>();
public QueryStringParameters(String query)
{
if (query.StartsWith("?"))
if (query.StartsWith("?",StringComparison.InvariantCulture))
query = query.Substring(1);
String[] pairs = query.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
@ -21,45 +22,40 @@ namespace appsrv.http
String[] kv = pair.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
string key = Uri.UnescapeDataString(kv[0].Replace('+',' '));
string value = kv.Length == 2 ? Uri.UnescapeDataString(kv[1].Replace('+',' ')) : "";
if (!key.Equals(String.Empty)){
parameters[key] = value;
}
MIMEMessage param = new MIMEMessage();
param.Value = value;
param.Headers.Add(new MimeHeader("content-disposition", String.Format("query-data; name=\"{0}\"",key)));
MIMEMessages.Add(param);
}
}
public string this[string key] {
get => parameters[key];
set => throw new NotImplementedException(); }
public MIMEMessage this[string key] {
get => Get(key);
set => throw new NotImplementedException();
}
public ICollection<string> Keys => parameters.Keys;
public ICollection<string> Values => parameters.Values;
public int Count => parameters.Count;
public bool IsReadOnly => true;
public void Add(string key, string value) => throw new NotImplementedException();
public void Add(KeyValuePair<string, string> item) => throw new NotImplementedException();
public void Clear() => throw new NotImplementedException();
public bool Remove(string key) => throw new NotImplementedException();
public bool Remove(KeyValuePair<string, string> item) => throw new NotImplementedException();
public bool Contains(KeyValuePair<string, string> item) => parameters.Contains(item);
public bool ContainsKey(string key) => parameters.ContainsKey(key);
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) => ((IDictionary<string, string>)parameters).CopyTo(array, arrayIndex);
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => parameters.GetEnumerator();
public bool TryGetValue(string key, out string value) => parameters.TryGetValue(key, out value);
IEnumerator IEnumerable.GetEnumerator() => parameters.GetEnumerator();
public override string ToString()
public MIMEMessage Get(String key)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("[Query");
foreach (String key in parameters.Keys){
stringBuilder.AppendFormat(" {0}={1}", key, parameters[key]);
foreach (MIMEMessage v in MIMEMessages)
{
if (v.Headers.Contains("content-disposition") &&
v.Headers.GetHeaderByName("content-disposition").ContainsParameter("NAME") &&
key.Equals(v.Headers.GetHeaderByName("content-disposition").GetParameter("NAME"))
)
return v;
}
stringBuilder.Append("]");
return null;
}
return stringBuilder.ToString();
public IEnumerator<MIMEMessage> GetEnumerator()
{
return MIMEMessages.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)MIMEMessages).GetEnumerator();
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace appsrv.http
{
public static class StringExtensions
{
public static String[] SplitWhitespace(this String line)
{
LinkedList<String> tokens = new LinkedList<string>();
StringBuilder sb = new StringBuilder();
for (int n = 0; n < line.Length;)
{
for (; n < line.Length && !Char.IsWhiteSpace(line[n]); n++)
sb.Append(line[n]);
tokens.AddLast(sb.ToString());
sb.Clear();
for (; n < line.Length && Char.IsWhiteSpace(line[n]); n++) { }
}
return tokens.ToArray();
}
public static String ToMark(this string me, char mark)
{
int i = me.IndexOf(mark);
if (i == -1)
return me;
return me.Substring(0,i);
}
public static String FromMark(this string me, char mark)
{
int i = me.IndexOf(mark);
if (i == -1)
return "";
return me.Substring(i + 1);
}
}
}

View File

@ -0,0 +1,140 @@
using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Collections;
using System.Runtime.InteropServices;
namespace appsrv.mime
{
public static class ArrayExtensions
{
public static int Find<T>(this T[] me, T[] pattern) where T : class
{
for (int n = 0; n < (me.Length - pattern.Length); n++)
{
for (int p = 0; p < pattern.Length; p++)
{
if (pattern[p] != me[n + p])
break;
}
}
return -1;
}
public static bool SequenceEquals(this byte[] me, byte[] cmp)
{
if (me.Length != cmp.Length)
return false;
for (int n = 0; n < me.Length; n++)
{
if (me[n] != cmp[n])
return false;
}
return true;
}
public static int Find(this byte[] me, byte[] pattern)
{
return Find(me, pattern, 0);
}
public static int Find(this byte[] me, byte[] pattern, int start)
{
for (int n = start; n < (me.Length - pattern.Length); n++)
{
int p;
for (p = 0; p < pattern.Length; p++)
{
if (pattern[p] != me[n + p])
break;
}
if (p == pattern.Length)
return n;
}
return -1;
}
public static IEnumerable<byte[]> Split(this byte[] me,byte[] pattern)
{
return new ByteArrayEnumerable(me, pattern);
}
class ByteArrayEnumerable : IEnumerable<byte[]>
{
public byte[] Source { get; }
public byte[] Pattern { get; }
public ByteArrayEnumerable(byte[] me, byte[] pattern)
{
Source = me;
Pattern = pattern;
}
public IEnumerator<byte[]> GetEnumerator()
{
return new ByteArraySplitter(Source, Pattern);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class ByteArraySplitter : IEnumerator<byte[]>
{
public byte[] Source { get; }
public byte[] Pattern { get; }
public int CurrentIndex { get; protected set; }
public ByteArraySplitter(byte[] source,byte[] pattern)
{
Source = source;
Pattern = pattern;
CurrentIndex = 0;
}
public byte[] Current { get; protected set; }
object IEnumerator.Current => Current;
public void Dispose()
{
Current = null;
}
public bool MoveNext()
{
if (CurrentIndex >= Source.Length)
return false;
int p = Source.Find(Pattern, CurrentIndex);
if (p == -1)
{
Current = new byte[Source.Length - CurrentIndex];
} else {
Current = new byte[p - CurrentIndex];
}
Array.Copy(Source, CurrentIndex, Current, 0, Current.Length);
CurrentIndex += Current.Length + Pattern.Length;
return true;
}
public void Reset()
{
CurrentIndex = 0;
Current = null;
}
}
}
}

159
mime/MIMEHeaders.cs 100644
View File

@ -0,0 +1,159 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace appsrv.mime
{
public class MimeHeaders : IEnumerable<MimeHeader>
{
List<MimeHeader> mimeHeaders = new List<MimeHeader>();
public MimeHeaders()
{
}
public MimeHeaders(Stream stream)
{
ReadHeaders(stream);
}
private void ReadHeaders(Stream stream)
{
do
{
byte[] lineBytes = stream.ReadLine();
if (lineBytes == null)
{
Console.WriteLine("MIMEHeaders: ReadHeaders(): reached end of stream");
break;
}
string rawLine = Encoding.ASCII.GetString(lineBytes);
if (rawLine == null)
throw new EndOfStreamException("reached end of stream while reading MIME headers");
if (String.Empty.Equals(rawLine))
break;
string[] kvp = rawLine.Split(new char[] { ':' }, 2);
if (kvp.Length != 2)
throw new FormatException(String.Format("invalid header line: {0}", rawLine));
string name = kvp[0].Trim().ToUpper();
MimeHeader mimeHeader = new MimeHeader(name);
MimeReader.DecodeRawHeaderValue(mimeHeader,kvp[1]);
Add(mimeHeader);
} while (true);
}
public MimeHeader this[string name] => GetHeaderByName(name);
public bool Contains(string name)
{
name = name.ToUpper();
foreach (MimeHeader header in mimeHeaders)
{
if (header.Name.Equals(name))
return true;
}
return false;
}
public MimeHeader GetHeaderByName(string name)
{
name = name.ToUpper();
foreach (MimeHeader header in mimeHeaders)
{
if (header.Name.Equals(name))
return header;
}
return null;
}
public MimeHeader[] GetHeadersByName(string name)
{
List<MimeHeader> headers = new List<MimeHeader>();
name = name.ToUpper();
foreach (MimeHeader header in mimeHeaders)
{
if (header.Name.Equals(name))
headers.Add(header);
}
return headers.ToArray();
}
public void Add(String name,String value)
{
Add(new MimeHeader(name, value));
}
public void Add(MimeHeader mimeHeader)
{
mimeHeaders.Add(mimeHeader);
}
public void Remove(MimeHeader header)
{
this.mimeHeaders.Remove(header);
}
public void Remove(string name)
{
Remove(name, false);
}
public void Remove(string name,bool removeAll)
{
foreach (MimeHeader header in mimeHeaders)
{
if (header.Name.Equals(name))
{
mimeHeaders.Remove(header);
if (!removeAll)
return;
}
}
}
public void Add(IEnumerable<MimeHeader> headers)
{
foreach (MimeHeader mh in headers)
Add(mh);
}
public void Set(string name,string value)
{
Remove(name, true);
Add(name, value);
}
public void Set(MimeHeader header)
{
Remove(header.Name, true);
Add(header);
}
public MimeHeader[] ToArray()
{
return mimeHeaders.ToArray();
}
public IEnumerator<MimeHeader> GetEnumerator()
{
return mimeHeaders.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)mimeHeaders).GetEnumerator();
}
}
}

169
mime/MIMEMessage.cs 100644
View File

@ -0,0 +1,169 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.IO;
namespace appsrv.mime
{
public class MIMEMessage
{
public MIMEMessage Container { get; protected set; }
public MimeHeaders Headers { get; }
public byte[] RawContent { get; set; }
public Encoding Encoding { get; set; }
public MIMEMessage[] Parts => parts.ToArray();
List<MIMEMessage> parts = new List<MIMEMessage>();
public MimeType ContentType {
get {
return null;
// return new MimeType(Headers.GetHeaderByName("CONTENT-TYPE").Value);
}
}
public MIMEMessage()
{
Container = null;
Headers = new MimeHeaders();
Encoding = Encoding.UTF8;
}
public MIMEMessage(MIMEMessage container)
{
Container = container;
Headers = new MimeHeaders();
Encoding = Encoding.UTF8;
}
public MIMEMessage(byte[] source)
:this(new MemoryStream(source))
{
}
public MIMEMessage(Stream stream,bool httpMode = false)
{
Encoding = Encoding.ASCII;
Headers = new MimeHeaders(stream);
ReadRawContent(stream,httpMode);
if (!Headers.Contains("content-type"))
{
Headers.Set("content-type", "text/plain");
}
if (Headers.GetHeaderByName("content-type").Value.ToUpper().StartsWith("MULTIPART/", StringComparison.InvariantCulture))
{
DecodeMultipart();
}
else if (Headers.GetHeaderByName("content-type").Value.ToUpper().StartsWith("TEXT/", StringComparison.InvariantCulture))
{
MimeHeader ctype = Headers.GetHeaderByName("content-type");
if (ctype.ContainsParameter("charset"))
{
Encoding = Encoding.GetEncoding(ctype.GetParameter("charset"));
}
else
{
//Encoding = Encoding.GetEncoding("ISO-8859-1");
Encoding = Encoding.UTF8;
}
}
}
private void ReadRawContent(Stream stream,bool httpMode)
{
if (Headers.Contains("content-length"))
{
int length = int.Parse(Headers.GetHeaderByName("content-length").Value);
RawContent = new byte[length];
stream.Read(RawContent, 0, length);
} else if (!httpMode){
MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
RawContent = new byte[memoryStream.Length];
memoryStream.Read(RawContent, 0, RawContent.Length);
} else {
RawContent = new byte[0];
}
}
private void DecodeMultipart()
{
byte[] rawBoundary = Encoding.ASCII.GetBytes(
String.Format("--{0}", Headers.GetHeaderByName("Content-Type").GetParameter("Boundary"))
);
byte[] boundary = Encoding.ASCII.GetBytes(
String.Format("\r\n--{0}", Headers.GetHeaderByName("Content-Type").GetParameter("Boundary"))
);
byte[] lastBoundary = new byte[] { 45, 45, 13, 10 };
Console.WriteLine("-----------[ RawContent ]-------------");
Console.WriteLine(Encoding.UTF8.GetString(RawContent));
Console.WriteLine("--------------------------------------");
foreach (byte[] rawpart in RawContent.Split(boundary))
{
int p = rawpart.Find(rawBoundary);
if (rawpart.SequenceEquals(lastBoundary))
break;
MemoryStream partStream = new MemoryStream(rawpart);
/*
* First boundary may miss \r\n... so we skip it manually
*/
if (p != -1)
{
partStream.Seek(p + rawBoundary.Length, SeekOrigin.Current);
}
Console.WriteLine("------------[ RAWPART ]---------------");
Console.WriteLine(Encoding.UTF8.GetString(rawpart));
Console.WriteLine("--------------------------------------");
partStream.ReadLine();
MIMEMessage part = new MIMEMessage(partStream);
AddPart(part);
}
}
public String Value
{
get {
return Encoding.GetString(RawContent);
}
set {
RawContent = Encoding.GetBytes(value);
}
}
public void AddPart(MIMEMessage part)
{
parts.Add(part);
part.Container = this;
}
public void RemovePart(MIMEMessage part)
{
parts.Remove(part);
part.Container = null;
}
}
}

85
mime/MimeHeader.cs 100644
View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace appsrv.mime
{
public class MimeHeader
{
public String Name { get; set; }
public String Value { get; set; }
Dictionary<string, string> parameters = new Dictionary<string, string>();
public MimeHeader(String name)
{
Name = name;
Value = "";
}
public MimeHeader(String name, String value)
:this(name,value,null)
{
}
public MimeHeader(String name,String value,IEnumerable<KeyValuePair<string,string>> parameters)
{
Name = name.ToUpper();
MimeReader.DecodeRawHeaderValue(this, value);
if (parameters != null)
{
foreach (KeyValuePair<string, string> kvp in parameters)
this.parameters.Add(kvp.Key,kvp.Value);
}
}
public string this[string key]
{
get => GetParameter(key);
set => SetParameter(key, value);
}
public string GetParameter(string key)
{
return this.parameters[key.ToUpper()];
}
public void SetParameter(string key,string value)
{
this.parameters[key.ToUpper()] = value;
}
public bool ContainsParameter(string key)
{
return this.parameters.ContainsKey(key.ToUpper());
}
public string RawValue {
get {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(Value);
foreach (KeyValuePair<string, string> kvp in parameters)
{
stringBuilder.AppendFormat(";{0}={1}", kvp.Key, kvp.Value);
}
return stringBuilder.ToString();
}
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendFormat("{0}: {1}", Name, Value);
foreach (KeyValuePair<string,string> kvp in parameters)
{
stringBuilder.AppendFormat(";{0}={1}", kvp.Key, kvp.Value);
}
return stringBuilder.ToString();
}
}
}

View File

@ -9,7 +9,9 @@ namespace appsrv.mime
{"html","text/html"},
{"htm","text/html"},
{"xml","text/xml"},
{"txt","text/plain"}
{"txt","text/plain"},
{"css","text/css"},
{"js","text/javascript"}
};

181
mime/MimeReader.cs 100644
View File

@ -0,0 +1,181 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace appsrv.mime
{
[Flags]
public enum MimeHeaderReadOptions {
NONE,
CONCAT_DUPLICATE_KEYS
}
public class MimeReader
{
public MimeStreamReader Reader { get; }
public MimeReader(TextReader reader)
{
Reader = new MimeStreamReader(reader);
}
public MimeReader(Stream stream){
Reader = new MimeStreamReader(stream);
}
public MIMEMessage ReadMessage()
{
MIMEMessage message = new MIMEMessage();
IEnumerable<MimeHeader> headers = ReadHeaders(MimeHeaderReadOptions.NONE);
message.Headers.Add(headers);
if (message.ContentType.Type.Equals("MULTIPART"))
{
ReadParts(message);
}
else if (message.Headers.Contains("Content-Length"))
{
int length = int.Parse(message.Headers.GetHeaderByName("Content-Length").Value);
}
return message;
}
public void ReadParts(MIMEMessage message)
{
string b = message.Headers.GetHeaderByName("Content-Type").GetParameter("BOUNDARY");
StringBuilder stringBuilder = new StringBuilder();
string endofline = null;
do
{
String part = ReadPart(b);
Console.WriteLine("PART: __|{0}|__", part);
endofline = Reader.ReadLine();
} while ((endofline != null) && (!endofline.Equals("--")));
}
private string ReadPart(String boundary)
{
string lookup = String.Format("\r\n--{0}", boundary);
return Reader.ReadToBoundary(lookup);
}
public IEnumerable<MimeHeader> ReadHeaders()
{
return ReadHeaders(MimeHeaderReadOptions.NONE);
}
public IEnumerable<MimeHeader> ReadHeaders(MimeHeaderReadOptions readOptions)
{
Dictionary<string, MimeHeader> readHeaders = new Dictionary<string, MimeHeader>();
do
{
string rawLine = Reader.ReadLine();
if (rawLine == null)
throw new EndOfStreamException("reached end of stream while reading MIME headers");
if (String.Empty.Equals(rawLine))
break;
string[] kvp = rawLine.Split(new char[] { ':' }, 2);
if (kvp.Length != 2)
throw new FormatException(String.Format("invalid header line: {0}",rawLine));
string name = kvp[0].Trim().ToUpper();
string[] valueTokens = kvp[1].Split(new char[] { ';' });
string value = valueTokens[0];
MimeHeader mimeHeader = new MimeHeader(name, value);
if (((readOptions & MimeHeaderReadOptions.CONCAT_DUPLICATE_KEYS) == MimeHeaderReadOptions.CONCAT_DUPLICATE_KEYS) && readHeaders.ContainsKey(name))
{
mimeHeader = readHeaders[name];
mimeHeader.Value = String.Format("{0},{1}", mimeHeader.Value, value);
}
else
{
mimeHeader = new MimeHeader(name, value);
}
for (int n = 1; n < valueTokens.Length;n++)
{
string[] partokens = valueTokens[n].Split(new char[] { '=' }, 2);
mimeHeader.SetParameter(partokens[0].Trim().ToUpper(), partokens[1].Trim());
}
} while (true);
return readHeaders.Values;
}
public static void DecodeRawHeaderValue(MimeHeader mimeHeader,string rawValue)
{
string[] rawTokens = rawValue.Split(new char[] { ';' });
mimeHeader.Value = rawTokens[0].Trim();
for (int n = 1; n < rawTokens.Length; n++)
{
string[] partokens = rawTokens[n].Split(new char[] { '=' }, 2);
if (partokens.Length == 2)
{
string pval = partokens[1].Trim();
if (pval.StartsWith("\"") && pval.EndsWith("\""))
pval = pval.Substring(1, pval.Length - 2);
mimeHeader.SetParameter(partokens[0].Trim().ToUpper(), pval);
}
else
{
mimeHeader.SetParameter(partokens[0].Trim().ToUpper(), "");
}
}
}
static readonly int CR = 0x0d;
static readonly int LF = 0x0a;
public static string ReadLineFromStream(Stream stream,Encoding encoding)
{
byte[] bytes = ReadLineFromStream(stream);
if (bytes != null)
return encoding.GetString(bytes);
return null;
}
public static byte[] ReadLineFromStream(Stream stream)
{
List<byte> line = new List<byte>();
int rb = 0;
while (rb != LF)
{
rb = stream.ReadByte();
if (rb < 0){
if (line.Count == 0)
return null;
break;
}
line.Add((byte)rb);
}
return line.ToArray();
}
}
}

87
mime/MimeStream.cs 100644
View File

@ -0,0 +1,87 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace appsrv.mime
{
public class MimeStreamReader
{
public Stream Stream { get; }
public TextReader TextReader { get; }
Stack<char> pushbacks = new Stack<char>();
public MimeStreamReader(Stream stream)
{
Stream = stream;
TextReader = new StreamReader(Stream, Encoding.ASCII);
}
public MimeStreamReader(TextReader textReader)
{
Stream = null;
TextReader = textReader;
}
public int Read()
{
if (pushbacks.Count > 0)
{
return pushbacks.Pop();
}
return TextReader.Read();
}
public void Push(char ch)
{
pushbacks.Push(ch);
}
public string ReadToBoundary(string boundary)
{
StringBuilder stringBuilder = new StringBuilder();
char[] buffer = new char[boundary.Length];
for (int n = 0; n < buffer.Length; n++)
{
int ch = Read();
if (ch == -1)
{
for (int p = 0; p < n; p++)
{
stringBuilder.Append(buffer[p]);
}
break;
}
buffer[n] = (char)ch;
if (buffer[n] != boundary[n])
{
while (n > 0)
{
Push(buffer[n--]);
}
stringBuilder.Append(buffer[n--]);
}
}
return stringBuilder.ToString();
}
public string ReadLine()
{
return ReadToBoundary("\r\n");
}
public string Read(int n)
{
char[] buffer = new char[n];
TextReader.ReadBlock(buffer, 0, n);
return new string(buffer);
}
}
}

22
mime/MimeType.cs 100644
View File

@ -0,0 +1,22 @@
using System;
namespace appsrv.mime
{
public class MimeType
{
public string Type { get; }
public string SubType { get; }
public MimeType(string contentType)
{
string[] t = contentType.Split(new char[] { '/' }, 2);
Type = t[0].ToUpper();
SubType = t[1].ToUpper();
}
public MimeType(string type,string subtype)
{
Type = type;
SubType = subtype;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
namespace appsrv.mime
{
public static class StreamExtensions
{
static readonly int CR = 0x0d;
static readonly int LF = 0x0a;
public static string ReadLine(this Stream stream, Encoding encoding)
{
byte[] bytes = ReadLine(stream);
if (bytes != null)
return encoding.GetString(bytes);
return null;
}
public static byte[] ReadLine(this Stream stream)
{
List<byte> line = new List<byte>();
int rb = 0;
while (rb != LF)
{
rb = stream.ReadByte();
if (rb < 0)
{
if (line.Count == 0)
return null;
break;
}
if ((rb != CR) && (rb != LF))
line.Add((byte)rb);
}
return line.ToArray();
}
}
}

4
packages.config 100644
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net461" />
</packages>

View File

@ -3,13 +3,14 @@ using System.IO;
using appsrv.server;
using System.Collections.Generic;
using appsrv.exceptions;
using System.Text;
using appsrv.templates;
namespace appsrv.resources
{
public class DirectoryResource : Resource
{
public DirectoryInfo DirectoryInfo { get; }
public bool IndexingEnabled { get; set; }
public DirectoryResource(DirectoryInfo directoryInfo)
:this(directoryInfo,null)
@ -19,38 +20,74 @@ namespace appsrv.resources
:base(directoryInfo.Name,container)
{
DirectoryInfo = directoryInfo;
Console.WriteLine("RES: + {0}", this);
}
public override void Hit(Stack<string> requestPath, HttpRequest request)
public override Resource Lookup(String name){
Resource resource = base.Lookup(name);
if (resource != null)
{
return resource;
}
String newName = System.IO.Path.Combine(DirectoryInfo.FullName, name);
if (Directory.Exists(newName))
{
return new DirectoryResource(new DirectoryInfo(newName), this);
}
if (File.Exists(newName))
{
FileInfo fileInfo = new FileInfo(newName);
if (fileInfo.Extension.Equals(".hfrm")){
return new Template(fileInfo.FullName, this);
} else {
return new FileResource(fileInfo, this);
}
}
//throw new ResourceNotFoundException(Path, name);
return null;
}
public override string[] List()
{
List<String> result = new List<string>();
result.AddRange(base.List());
result.AddRange(Directory.GetFiles(DirectoryInfo.FullName));
result.AddRange(Directory.GetDirectories(DirectoryInfo.FullName));
String[] list = new string[result.Count];
for (int n = 0; n < list.Length;n++)
{
list[n] = System.IO.Path.GetFileName(result[n]);
}
return list;
}
public override void Hit(Stack<string> requestPath, Request request)
{
if (requestPath.Count > 0)
{
String nextResourceName = requestPath.Pop();
Resource nextResource = Lookup(nextResourceName);
DirectoryInfo[] directoryInfos = DirectoryInfo.GetDirectories(nextResourceName);
if (directoryInfos.Length == 1){
DirectoryResource directoryResource = new DirectoryResource(directoryInfos[0],this);
directoryResource.Request(requestPath, request);
} else {
FileInfo[] fileInfos = DirectoryInfo.GetFiles(nextResourceName);
if (fileInfos.Length == 1){
FileResource fileResource = new FileResource(fileInfos[0],this);
fileResource.Request(requestPath, request);
} else {
throw new ResourceNotFoundException(Path, nextResourceName);
}
}
if (nextResource == null)
throw new FileNotFoundException();
nextResource.Request(requestPath, request);
} else {
if (IndexingEnabled){
// ToDo: Create index...
} else {
base.Hit(requestPath, request);
}
base.Hit(requestPath, request);
}
}
public override string ToString()
{
return String.Format("[DirectoryResource Path={0}]",DirectoryInfo.FullName);
}
}
}

View File

@ -18,17 +18,17 @@ namespace appsrv.resources
FileInfo = fileInfo;
}
public override void Hit(Stack<string> requestPath, HttpRequest request)
public override void Hit(Stack<string> requestPath, Request request)
{
if ((requestPath.Count > 0) && !DiscardRequestPath){
throw new ApplicationServerException(String.Format("No resources below {0}",Path));
} else {
request.SetResponseHeader("Content-Type", MimeHelper.GuessMIMEFromFilename(FileInfo.Name));
Response response = request.Response;
response.Headers.Add("Content-type", MimeHelper.GuessMIMEFromFilename(FileInfo.Name));
using (FileStream fileStream = new FileStream(FileInfo.FullName,FileMode.Open))
{
fileStream.CopyTo(request.ResponseStream);
fileStream.CopyTo(response.Stream);
fileStream.Close();
}
}

View File

@ -4,16 +4,25 @@ using System.Collections.Generic;
using System.Dynamic;
using appsrv.exceptions;
using System.Linq;
using System.Text;
using appsrv.templates;
namespace appsrv.resources
{
public abstract class Resource
{
private Application application;
public Resource Container { get; }
public String Name { get; }
public Resource DefaultResource { get; set; }
public bool IndexingEnabled { get; set; }
public virtual DateTime LastModification { get; protected set; } = DateTime.Now;
Dictionary<String, Resource> resources = new Dictionary<string, Resource>();
public Resource(String name)
{
Name = name;
@ -26,6 +35,32 @@ namespace appsrv.resources
container.Add(this);
}
public Application Application {
get {
if (Container != null)
return Container.Application;
return application;
}
set {
application = value;
}
}
public virtual Resource Lookup(String name)
{
if (resources.ContainsKey(name))
return resources[name];
if (Container != null)
return Container.Lookup(name);
return null;
}
public virtual String[] List()
{
return resources.Keys.ToArray();
}
protected virtual void Add(Resource resource){
resources.Add(resource.Name, resource);
}
@ -35,16 +70,16 @@ namespace appsrv.resources
}
}
public bool Contains(String resName)
public virtual bool Contains(String resName)
{
return resources.ContainsKey(resName);
return Lookup(resName) != null;
}
public bool Contains(Resource resource)
public virtual bool Contains(Resource resource)
{
return resources.ContainsValue(resource);
return (resource.Container == this) && (Lookup(resource.Name) == resource);
}
public ISet<Resource> Resources { get => new HashSet<Resource>(resources.Values); }
public virtual ISet<Resource> Resources => new HashSet<Resource>(resources.Values);
public Resource Root {
get {
@ -74,35 +109,52 @@ namespace appsrv.resources
}
}
public virtual Resource this[string name]{
get => resources[name];
public T ThrowIfNull<T,E>(T value) where T: class where E: Exception
{
if (value == null)
throw Activator.CreateInstance<E>();
return value;
}
public virtual void Request(Stack<String> requestPath, HttpRequest request)
public virtual Resource this[string name]{
get => ThrowIfNull<Resource, KeyNotFoundException>(Lookup(name));
}
public virtual void Request(Stack<String> requestPath, Request request)
{
try
{
if ((requestPath.Count > 0) && (Contains(requestPath.Peek())))
{
this[requestPath.Pop()].Request(requestPath, request);
Lookup(requestPath.Pop()).Request(requestPath, request);
}
else
{
Hit(requestPath, request);
if ((requestPath.Count == 0) && (DefaultResource != null))
{
DefaultResource.Request(requestPath, request);
}
else
{
Hit(requestPath, request);
}
}
}
catch (ApplicationServerException ase)
{
HandleException(ase);
HandleException(ase,request);
}
}
public virtual void Hit(Stack<String> requestPath, HttpRequest request){
public virtual void Hit(Stack<String> requestPath, Request request){
if (requestPath.Count > 0){
throw new ResourceNotFoundException(Path, requestPath.Peek());
} else {
throw new ApplicationServerException("unimplemented resource has been hit");
if (IndexingEnabled){
CreateIndex(request);
} else {
throw new ApplicationServerException("unimplemented resource has been hit");
}
}
}
@ -132,10 +184,39 @@ namespace appsrv.resources
protected void HandleException(ApplicationServerException ase){
Console.WriteLine("ASE: " + ase.ToString());
protected void HandleException(ApplicationServerException ase,Request request){
Response response = request.Response;
response.StatusCode = 500;
response.Headers.Add("Content-type", "text/html");
response.Writer.WriteLine("<html><head><title>Exception caught: {0}</title></head><body>",ase);
response.Writer.WriteLine("<h1>Exception caught: {0}</h1><p>{1}</p>",ase,ase.Message);
response.Writer.WriteLine("<p><code>{0}</code></p></body></html>",ase.StackTrace);
}
protected void CreateIndex(Request request){
StringBuilder stringBuilder = new StringBuilder();
Response response = request.Response;
stringBuilder.AppendFormat("<html><head><title>{0}</title></head>", Path);
stringBuilder.AppendFormat("<body><h1>Index of {0}</h1>", Path);
foreach (String name in List())
{
String p = Path;
if (p.Equals("/"))
p = "";
stringBuilder.AppendFormat("<a href=\"{0}/{1}\">{1}</a><br/>", p, name);
}
stringBuilder.Append("</body></html>");
response.Headers.Add("Content-type", "text/html");
response.Writer.Write(stringBuilder.ToString());
}
public virtual void ApplyParameters(Dictionary<String,String> parameters)
{
}
}
}

View File

@ -19,7 +19,7 @@ namespace appsrv.resources
throw new ArgumentOutOfRangeException("This resource can't have children");
}
public override void Request(Stack<string> requestPath, HttpRequest request)
public override void Request(Stack<string> requestPath, Request request)
{
Target.Request(requestPath, request);
}

View File

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using appsrv.attributes;
using appsrv.server;
using System.Text;
namespace appsrv.resources
{
public class StaticClassResource : Resource
{
Type Type { get; }
Dictionary<string, FieldInfo> callableFields = new Dictionary<string, FieldInfo>();
public StaticClassResource(Type type,Resource container)
:base(type.Name,container)
{
Type = type;
foreach (MethodInfo methodInfo in Type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)){
WebCallable webCallable = methodInfo.GetCustomAttribute<WebCallable>();
if (webCallable != null){
new CallableMethodResource(methodInfo, this);
}
}
}
class CallableMethodResource : Resource
{
MethodInfo MethodInfo { get; }
WebCallable WebCallable { get; }
public CallableMethodResource(MethodInfo methodInfo,Resource container)
:base(
methodInfo.GetCustomAttribute<WebCallable>() != null ?
(methodInfo.GetCustomAttribute<WebCallable>().Name != null) ? methodInfo.GetCustomAttribute<WebCallable>().Name : methodInfo.Name
:methodInfo.Name,
container
)
{
MethodInfo = methodInfo;
WebCallable = methodInfo.GetCustomAttribute<WebCallable>();
}
private object[] ReadParameters(HttpRequest request)
{
ParameterInfo[] parameters = MethodInfo.GetParameters();
object[] p = new object[ parameters.Length ];
for (int n = 0; n < parameters.Length;n++)
{
ParameterInfo parameterInfo = parameters[n];
object pvalue = request.Query[parameterInfo.Name];
p[n] = Convert.ChangeType(pvalue, parameterInfo.ParameterType );
}
return p;
}
private void SerializeResult(HttpRequest request,object result){
switch (WebCallable.Serialization){
case Serialization.PLAIN:
SerializePlain(request, result);
break;
case Serialization.JSON:
break;
case Serialization.XML:
break;
}
}
private void SerializePlain(HttpRequest request,object result){
request.SetResponseHeader("Content-Type", "text/plain");
byte[] plain = Encoding.UTF8.GetBytes(result.ToString());
request.ResponseStream.Write(plain, 0, plain.Length);
}
public override void Hit(Stack<string> requestPath, HttpRequest request)
{
object[] p = ReadParameters(request);
object result = MethodInfo.Invoke(null, p);
SerializeResult(request, result);
}
}
}
}

View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using appsrv.server;
using appsrv.templates;
using System.Reflection;
using appsrv.mime;
namespace appsrv.resources.reflection
{
public partial class ReflectiveResource
{
class ReflectiveEdit
{
public ReflectiveResource Resource { get; }
public Guid ID { get; } = Guid.NewGuid();
public object Value { get; }
public Template Template { get; }
public DateTime LastUsage { get; protected set; }
public bool TimedOut => (DateTime.Now - LastUsage) > TimeSpan.FromSeconds(300);
public ReflectiveEdit(ReflectiveResource resource, object value, Template template)
{
Resource = resource;
Value = value;
Template = template;
Touch();
}
public void Touch()
{
LastUsage = DateTime.Now;
}
public void Render(Request request)
{
Touch();
ApplyUpdates(request);
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["REDIT_PATH"] = String.Format("{0}/${1}", Resource.Path, ID.ToString());
Template.Render(request, Value, parameters);
}
public void ApplyUpdates(Request request)
{
foreach (FieldInfo fieldInfo in Value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public))
{
string formKey = String.Format("this_{0}", fieldInfo.Name);
MIMEMessage p = request.GetParameter(formKey);
if (p != null)
{
try
{
object fieldValue = Convert.ChangeType(p.Value, fieldInfo.FieldType);
fieldInfo.SetValue(Value, fieldValue);
}
catch (InvalidCastException ice)
{
Console.WriteLine("ReflectiveEdit: Invalid Cast for {0} assigning value {1}", fieldInfo, p.Value);
}
}
}
foreach (PropertyInfo propertyInfo in Value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!propertyInfo.CanWrite)
continue;
string formKey = String.Format("this_{0}", propertyInfo.Name);
MIMEMessage p = request.GetParameter(formKey);
if (p != null)
{
object fieldValue = Convert.ChangeType(p.Value, propertyInfo.PropertyType);
propertyInfo.SetValue(Value, fieldValue);
}
}
MIMEMessage mSubmit = request.GetParameter("REDIT_SUBMIT");
if (mSubmit != null)
{
if (mSubmit.Value.Equals("save"))
{
if (Resource.MethodSafe != null)
{
Resource.MethodSafe.Invoke(Resource.NativeInstance, new object[] { Value });
}
}
}
}
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace appsrv.resources.reflection
{
public partial class ReflectiveResource
{
class ReflectiveEditStore
{
public ReflectiveResource ReflectiveResource { get; }
Dictionary<Guid, ReflectiveEdit> edits = new Dictionary<Guid, ReflectiveEdit>();
public ReflectiveEditStore(ReflectiveResource resource)
{
ReflectiveResource = resource;
}
public ReflectiveEdit Find(Guid id)
{
Cleanup();
if (edits.ContainsKey(id))
return edits[id];
throw new KeyNotFoundException();
}
public void Add(ReflectiveEdit edit)
{
edits.Add(edit.ID, edit);
}
public void Cleanup()
{
foreach (Guid id in edits.Keys.ToArray())
{
if (edits[id].TimedOut)
edits.Remove(id);
}
}
}
}
}

View File

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.CodeDom;
using appsrv.server;
using System.IO;
using appsrv.templates;
using appsrv.templates.token;
using System.Reflection;
using appsrv.mime;
using System.Collections;
using Newtonsoft.Json;
namespace appsrv.resources.reflection
{
public partial class ReflectiveResource : Resource
{
Dictionary<Type, ReflectiveResourceCrawler> crawlers = new Dictionary<Type, ReflectiveResourceCrawler>();
Dictionary<String, Template> templates = new Dictionary<string, Template>();
public object NativeInstance { get; protected set; }
public Type NativeType { get; protected set; }
public String TemplatesPath { get; protected set; }
public MethodInfo MethodSafe { get; protected set; }
public ReflectiveResource(Type type, Resource container)
: this(type, container, type.Name)
{
}
public ReflectiveResource(Type type, Resource container,String name)
: base(name, container)
{
NativeType = type;
TemplatesPath = Application.BaseDirectory.FullName;
}
public ReflectiveResource(object instance, Resource container)
:this(instance,container,instance.GetType().Name)
{
}
public ReflectiveResource(object instance,Resource container,String name)
:base(name,container)
{
NativeInstance = instance;
NativeType = instance.GetType();
TemplatesPath = Application.BaseDirectory.FullName;
}
public ReflectiveResourceCrawler CrawlerForType(Type type)
{
if (!crawlers.ContainsKey(type))
{
crawlers.Add(type, new ReflectiveResourceCrawler(this,type));
}
return crawlers[type];
}
public object Crawl(Request request,Stack<String> pathStack)
{
return Crawl(NativeInstance, NativeType, request, pathStack);
}
public object Crawl(object intermediate,Type type,Request request,Stack<String> pathStack)
{
if ((intermediate != null) && (intermediate.GetType() != type))
{
throw new ArgumentException(String.Format("intermediate <{0}> needs to be of type <{1}> but is of type <{2}>",intermediate,type,intermediate.GetType()));
}
ReflectiveResourceCrawler crawler = CrawlerForType(type);
object next = crawler.Crawl(intermediate, request, pathStack);
if (pathStack.Count > 0)
{
return Crawl(next, next.GetType(), request, pathStack);
}
return next;
}
public override Resource Lookup(string name)
{
Resource resource = base.Lookup(name);
if (resource == null)
{
resource = GetTemplate(name);
}
return resource;
}
public override void Hit(Stack<string> requestPath, Request request)
{
ReflectiveEdit edit;
ReflectiveEditStore store;
if (requestPath.Peek().EndsWith(".hfrm"))
{
// TODO: How to handle external template requests...
} else if (requestPath.Peek()[0] == '$')
{
Guid editID = Guid.Parse(requestPath.Pop().Substring(1));
if (request.Session.ContainsKey<ReflectiveEditStore>(Path))
{
store = request.Session.Get<ReflectiveEditStore>(Path);
edit = store.Find(editID);
edit.Render(request);
}
else
{
throw new KeyNotFoundException();
}
}
else
{
object resourceObject = Crawl(request, requestPath);
if (resourceObject.GetType().IsPrimitive || (resourceObject is string))
{
Response response = request.Response;
response.Headers.Add("Content-type", "text/html");
response.Reference = resourceObject;
response.Writer.Write(resourceObject);
} else if (resourceObject is IEnumerable)
{
Response response = request.Response;
response.Headers.Add("Content-Type", "application/json");
response.Reference = resourceObject;
response.Writer.Write(JsonConvert.SerializeObject(resourceObject));
} else
{
string tmplName = String.Format("{0}.hfrm", resourceObject.GetType().FullName);
Template template = GetTemplate(tmplName);
edit = new ReflectiveEdit(this, resourceObject, template);
if (!request.Session.ContainsKey<ReflectiveEditStore>(Path))
{
store = new ReflectiveEditStore(this);
request.Session.Set<ReflectiveEditStore>(Path, store);
}
else
{
store = request.Session.Get<ReflectiveEditStore>(Path);
}
store.Add(edit);
edit.Render(request);
}
}
}
private Template GetTemplate(String tmplFileName){
if (!templates.ContainsKey(tmplFileName))
{
try
{
string tmplPath = String.Format("{0}/{1}", TemplatesPath, tmplFileName);
if (File.Exists(tmplPath))
templates.Add(tmplFileName, new Template(tmplPath, this));
else
return null;
} catch (FileNotFoundException)
{
return null;
}
}
return templates[tmplFileName];
}
public override void ApplyParameters(Dictionary<string, string> parameters)
{
if (parameters.ContainsKey("templates"))
{
TemplatesPath = parameters["templates"];
if (!System.IO.Path.IsPathRooted(TemplatesPath))
TemplatesPath = System.IO.Path.Combine(Application.BaseDirectory.FullName, TemplatesPath);
}
if (parameters.ContainsKey("instantiate"))
{
bool instantiate = (bool)Convert.ChangeType(parameters["instantiate"],typeof(bool));
if (instantiate)
{
NativeInstance = Activator.CreateInstance(NativeType);
}
}
if (parameters.ContainsKey("method_save"))
{
MethodSafe = NativeType.GetMethod(parameters["method_save"]);
}
}
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using appsrv.server;
using System.Reflection;
using appsrv.exceptions;
using appsrv.attributes;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
namespace appsrv.resources.reflection
{
public class ReflectiveResourceCrawler
{
/* Instance Members */
public ReflectiveResource Resource { get; }
public Type NativeType { get; }
public ReflectiveResourceCrawler(ReflectiveResource resource,Type type)
{
Resource = resource;
NativeType = type;
}
public object Crawl(object instance,Request request,Stack<String> pathStack)
{
if (pathStack.Count == 0)
return instance;
String next = pathStack.Pop();
foreach (FieldInfo fieldInfo in NativeType.GetFields())
{
if (fieldInfo.GetCustomAttribute<PublishedMemberAttribute>()!=null)
{
if (fieldInfo.Name.Equals(next))
{
return FixupIntermediate(fieldInfo.GetValue(instance), request, pathStack);
}
}
}
foreach (PropertyInfo propertyInfo in NativeType.GetProperties())
{
if (propertyInfo.GetCustomAttribute<PublishedMemberAttribute>() != null)
{
if (propertyInfo.Name.Equals(next))
{
return FixupIntermediate(propertyInfo.GetValue(instance), request, pathStack);
}
}
}
foreach (MethodInfo methodInfo in NativeType.GetMethods())
{
if (methodInfo.GetCustomAttribute<PublishedMemberAttribute>() != null)
{
if (methodInfo.Name.Equals(next))
{
object[] parameters = PopParameters(methodInfo,pathStack,request);
object i = methodInfo.IsStatic ? null : instance;
return FixupIntermediate( methodInfo.Invoke(i,parameters), request, pathStack);
}
}
}
throw new KeyNotFoundException(next);
}
private object[] PopParameters(MethodInfo methodInfo,Stack<String> pathStack,Request request)
{
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
object[] p = new object[parameterInfos.Length];
for (int n = 0; n < parameterInfos.Length;n++)
{
string nextparm = null;
if (request.HasParameter(parameterInfos[n].Name))
{
nextparm = request.GetParameterValue(parameterInfos[n].Name);
} else
{
if (pathStack.Count > 0)
{
nextparm = pathStack.Pop();
}
}
p[n] = Convert.ChangeType(nextparm, parameterInfos[n].ParameterType);
}
return p;
}
private object FixupIntermediate(object intermediate,Request request, Stack<String> pathStack)
{
if (intermediate.GetType().IsArray)
return FixupIntermediateArray((Array)intermediate, request, pathStack);
return intermediate;
}
private object FixupIntermediateArray(Array intermediate, Request request, Stack<String> pathStack)
{
if (pathStack.Count == 0)
return intermediate;
int index = int.Parse(pathStack.Pop());
return intermediate.GetValue(index);
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace appsrv.security
{
public class AccessMask
{
private ISet<AccessMask> children = new HashSet<AccessMask>();
public AccessMask Container { get; }
public String Key { get; }
public long Mask { get; }
public AccessMask(AccessMask container,long mask)
{
Container = container;
Mask = mask;
if (container != null)
container.children.Add(this);
}
public AccessMask this[string key]{
get {
foreach (AccessMask child in children)
{
if (child.Key.Equals(key))
return child;
}
throw new IndexOutOfRangeException();
}
}
}
}

12
security/Group.cs 100644
View File

@ -0,0 +1,12 @@
using System;
namespace appsrv.security
{
public class Group
{
public String Name { get; }
public Group()
{
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace appsrv.security
{
public interface ISecurityProvider
{
String[] GetUserList();
UserInfo Lookup(string login);
SecureUser Authenticate(String login, String passwd);
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Security.Policy;
namespace appsrv.security
{
public class SecureUser : UserInfo
{
public String Login { get; }
public String FullName { get; }
public string[] CustomKeys => throw new NotImplementedException();
public SecureUser(String username)
{
Login = username;
}
public string GetCustomKey(string key)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace appsrv.security
{
public class SecurityEntity
{
private ISet<SecurityEntity> securityGroups = new HashSet<SecurityEntity>();
public String Name { get; }
public ISet<SecurityEntity> MemberOf => new HashSet<SecurityEntity>(securityGroups);
public SecurityEntity(String name,IEnumerable<SecurityEntity> memberOf)
{
Name = name;
AddSecurityGroups(memberOf);
}
private void AddSecurityGroups(IEnumerable<SecurityEntity> secGroups)
{
foreach (SecurityEntity group in secGroups)
{
if (securityGroups.Add(group))
{
AddSecurityGroups(group.MemberOf);
}
}
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace appsrv.security
{
public interface UserInfo
{
String Login { get; }
String FullName { get; }
String[] CustomKeys { get; }
String GetCustomKey(string key);
}
}

View File

@ -1,13 +1,130 @@
using System;
using appsrv.resources;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Collections.Generic;
using appsrv.resources.reflection;
namespace appsrv.server
{
public class Application
{
public ApplicationServer ApplicationServer { get; private set; }
public String ConfigurationFileName { get; }
public DirectoryInfo BaseDirectory { get; }
public Application(ApplicationServer applicationServer)
public String ApplicationName { get; protected set; }
public String[] Aliases => aliases.ToArray();
public Resource RootResource { get; protected set; }
public ISet<Assembly> Assemblies { get; } = new HashSet<Assembly>();
List<string> aliases = new List<string>();
public Application(ApplicationServer applicationServer,String filename)
{
this.ApplicationServer = applicationServer;
ApplicationServer = applicationServer;
ConfigurationFileName = filename;
BaseDirectory = new FileInfo(filename).Directory;
LoadConfiguration();
}
private void LoadConfiguration()
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(ConfigurationFileName);
ApplicationName = xmlDocument.DocumentElement["ApplicationName"].InnerText.Trim();
foreach (XmlNode nAssembly in xmlDocument.DocumentElement.SelectNodes("Assembly"))
{
string assemblyfilename = Path.Combine(BaseDirectory.FullName, nAssembly.InnerText);
Assemblies.Add(Assembly.LoadFrom(assemblyfilename));
}
LoadResource(xmlDocument.DocumentElement["Resource"]);
}
public bool IdentifiedBy(string alias)
{
if (ApplicationName.Equals(alias, StringComparison.InvariantCultureIgnoreCase))
return true;
foreach (string a in aliases)
{
if (a.Equals(alias, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
public Type GetType(String typeName)
{
foreach (Assembly asm in Assemblies)
{
Type type = asm.GetType(typeName);
if (type != null)
return type;
}
return this.GetType().Assembly.GetType(typeName);
}
private void LoadResource(XmlNode xmlResource,Resource container = null){
String providerName = xmlResource["Provider"].GetAttribute("Name");
String providerKey = xmlResource["Provider"].InnerText.Trim();
Resource resource = null;
switch (providerName)
{
case "StaticDirectory":
resource = new DirectoryResource(new DirectoryInfo(Path.Combine(BaseDirectory.FullName,providerKey)),container);
break;
/* case "StaticClass":
resource = new StaticClassResource(GetType(providerKey), container);
break;
case "ClassInstance":
resource = new ClassInstanceResource(GetType(providerKey), container);
break;
*/
case "ReflectiveResource":
resource = new ReflectiveResource(GetType(providerKey), container);
break;
}
Dictionary<String, String> parameters = new Dictionary<string, string>();
foreach (XmlNode pnode in xmlResource.SelectNodes("Parameter"))
{
parameters.Add(pnode.Attributes["Name"].Value, pnode.InnerText);
}
resource.ApplyParameters(parameters);
if (container == null)
{
RootResource = resource;
RootResource.Application = this;
}
foreach (XmlNode subResource in xmlResource.SelectNodes("Resource"))
{
LoadResource(subResource, resource);
}
if (xmlResource.Attributes["Default"] != null){
resource.DefaultResource = resource.FindByPath(xmlResource.Attributes["Default"].InnerText);
}
}
}
}

View File

@ -2,40 +2,93 @@
using System.Collections.Generic;
using appsrv.resources;
using System.Linq;
using appsrv.sessions;
using sharp.logging;
namespace appsrv.server
{
public class ApplicationServer
{
public Resource DefaultRoot { get; set; }
public Application DefaultApplication => applications[0];
public Application[] Applications => applications.ToArray();
Dictionary<String, Resource> roots = new Dictionary<string, Resource>();
List<Application> applications = new List<Application>();
Dictionary<Guid, Session> sessions = new Dictionary<Guid, Session>();
public ApplicationServer()
{
}
public void AddRoot(String name,Resource rootResource){
roots.Add(name, rootResource);
if (DefaultRoot == null){
DefaultRoot = rootResource;
public void Add(Application application)
{
if (!applications.Contains(application))
applications.Add(application);
}
public void Remove(Application application)
{
applications.Remove(application);
}
public Application ApplicationByAlias(String alias)
{
foreach (Application application in applications)
{
if (application.IdentifiedBy(alias))
return application;
}
return DefaultApplication;
}
public void HandleRequest(Request request){
Resource rootResource = request.Application.RootResource;
Stack<String> requestPath = new Stack<string>(request.SplittedPath.Reverse());
Console.WriteLine("ApplicationServer.HandleRequest: {0}", request);
try
{
rootResource.Request(requestPath, request);
} catch (Exception e)
{
Logger.Default.Log(e);
}
}
public Resource FindRoot(String rootName)
public Session FindSession(Guid id)
{
if (roots.ContainsKey(rootName))
return roots[rootName];
return DefaultRoot;
lock (sessions)
{
if (sessions.ContainsKey(id))
return sessions[id];
return null;
}
}
public void HandleRequest(HttpRequest request){
Resource rootResource = FindRoot(request.Hostname);
Stack<String> requestPath = new Stack<string>(request.URI.AbsolutePath.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries).Reverse());
rootResource.Request(requestPath, request);
public void AddSession(Session session)
{
lock (sessions)
{
if (!sessions.ContainsKey(session.ID))
sessions.Add(session.ID, session);
}
}
/*
* TODO: Call from somewhere...
*/
private void CleanupSessions()
{
lock (sessions)
{
foreach (Session session in sessions.Values.ToArray())
{
if (session.TimedOut)
sessions.Remove(session.ID);
}
}
}
}
}

View File

@ -1,229 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using appsrv.exceptions;
using appsrv.http;
namespace appsrv.server
{
public class HttpRequest
{
private Stream stream;
private StreamReader streamReader;
private List<HttpRequest> currentRequests = new List<HttpRequest>();
private MemoryStream responseStream;
private StreamWriter responseWriter;
Dictionary<String, String> requestHeaders = new Dictionary<string, string>();
Dictionary<String, String> responseHeaders = new Dictionary<string, string>();
public EndPoint Client { get; private set; }
public ApplicationServer ApplicationServer { get; private set; }
public Uri URI { get; private set; }
public String Method { get; private set; }
public String RequestURL { get; private set; }
public String Protocol { get; private set; }
public String Hostname { get; private set; }
public int Port { get; private set; }
public QueryStringParameters Query { get; private set; }
public int StatusCode { get; set; } = 200;
public HttpRequest(ApplicationServer applicationServer, TcpClient client)
{
this.ApplicationServer = applicationServer;
this.Client = client.Client.RemoteEndPoint;
this.stream = client.GetStream();
this.streamReader = new StreamReader(this.stream);
String rLine = this.streamReader.ReadLine();
String[] rTokens = SplitWhitespace(
rLine
);
Console.WriteLine("Tokens: {0}", rTokens);
if (rTokens.Length != 3){
throw new IllegalRequestException(rLine);
}
this.Method = rTokens[0];
this.RequestURL = rTokens[1];
this.Protocol = rTokens[2];
}
public override string ToString()
{
return string.Format("[HttpRequest: Client={0}, ApplicationServer={1}, Hostname={6} Port={7} URI={2}, Method={3}, RequestURL={4}, Protocol={5} Query={8}]", Client, ApplicationServer, URI, Method, RequestURL, Protocol, Hostname, Port,Query);
}
public String GetRequestHeader(String name, String def = "")
{
name = name.ToLowerInvariant();
if (requestHeaders.ContainsKey(name))
return requestHeaders[name];
return def;
}
public String GetResponseHeader(String name, String def = "")
{
name = name.ToLowerInvariant();
if (responseHeaders.ContainsKey(name))
return responseHeaders[name];
return def;
}
public void SetResponseHeader(String name,String value){
name = name.ToLowerInvariant();
if (value == null)
{
this.responseHeaders.Remove(name);
}
else
{
this.responseHeaders[name] = value;
}
}
public Stream ResponseStream
{
get {
if (responseStream == null)
responseStream = new MemoryStream();
return responseStream;
}
}
public TextWriter ResponseWriter
{
get
{
if (this.responseWriter == null)
{
this.responseWriter = new StreamWriter(this.responseStream);
}
return this.responseWriter;
}
}
private String[] SplitWhitespace(String line){
LinkedList<String> tokens = new LinkedList<string>();
StringBuilder sb = new StringBuilder();
for (int n = 0; n < line.Length;)
{
for (; n < line.Length && !Char.IsWhiteSpace(line[n]); n++)
sb.Append(line[n]);
tokens.AddLast(sb.ToString());
sb.Clear();
for (; n < line.Length && Char.IsWhiteSpace(line[n]); n++) {}
}
return tokens.ToArray();
}
private void ReadHttpHeaders(){
String headerLine = this.streamReader.ReadLine();
String hName = null;
while (!headerLine.Equals(String.Empty)){
if (Char.IsWhiteSpace(headerLine[0]) && hName != null)
{
requestHeaders[hName] = requestHeaders[hName] + "," + headerLine.Trim();
} else {
String[] split = headerLine.Split(new char[] { ':' }, 2);
if (split.Length != 2){
throw new IllegalRequestException("malformed header");
}
hName = split[0].ToLowerInvariant();
requestHeaders[hName] = split[1];
}
headerLine = this.streamReader.ReadLine();
}
foreach (String hname in requestHeaders.Keys.ToArray()){
requestHeaders[hname] = requestHeaders[hname].Trim();
}
}
private void InterpretRequestHeaders()
{
String host = GetRequestHeader("host");
String[] hostTokens = host.Split(':');
Hostname = hostTokens[0];
if (hostTokens.Length > 1){
Port = int.Parse(hostTokens[1]);
}
URI = new Uri(String.Format("http://{0}:{1}/{2}", Hostname, Port, RequestURL));
Query = new QueryStringParameters(URI.Query);
}
private void SendResponse()
{
using (StreamWriter writer = new StreamWriter(this.stream))
{
ResponseStream.Position = 0;
SetResponseHeader("Content-Length", responseStream.Length.ToString());
writer.WriteLine("{0} {1} {2}", Protocol, StatusCode, HttpStatusCodes.GetStatusMessage(StatusCode));
foreach (String rhName in responseHeaders.Keys){
writer.WriteLine("{0}: {1}", rhName, responseHeaders[rhName]);
}
writer.WriteLine();
writer.Flush();
responseStream.CopyTo(this.stream);
}
}
public void Handle(){
this.ReadHttpHeaders();
this.InterpretRequestHeaders();
Console.WriteLine("Request Handled: {0}",this);
foreach (String key in this.requestHeaders.Keys){
Console.WriteLine("HH: {0} = {1}",key,this.requestHeaders[key]);
}
ApplicationServer.HandleRequest(this);
SendResponse();
this.streamReader.Close();
this.stream.Close();
}
}
}

89
server/Request.cs 100644
View File

@ -0,0 +1,89 @@
using System;
using appsrv.sessions;
using System.Reflection;
using appsrv.mime;
namespace appsrv.server
{
public abstract class Request
{
private Session _session = null;
public ApplicationServer ApplicationServer { get; }
public Application Application { get; protected set; }
public Session Session
{
get => (_session == null) ? CreateSession() : _session;
protected set => _session = value;
}
public String Path { get; protected set; }
public String[] SplittedPath => Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
public abstract string ROOT { get; }
public byte[] RawBody { get; protected set; }
public object Body { get; protected set; }
public Request(ApplicationServer applicationServer)
{
ApplicationServer = applicationServer;
}
public abstract Response Response { get; }
public abstract MIMEMessage GetParameter(String name);
public virtual bool HasParameter(String name){
return GetParameter(name) != null;
}
public virtual String GetParameterValue(String name){
MIMEMessage pm = GetParameter(name);
if (pm != null)
{
return pm.Value;
}
return null;
}
private Session CreateSession()
{
_session = new Session(ApplicationServer);
return _session;
}
protected void ApplySession(Guid SID)
{
Session = ApplicationServer.FindSession(SID);
}
public Response SubRequest(string path)
{
InternalRequest internalRequest = new InternalRequest(this,path);
ApplicationServer.HandleRequest(internalRequest);
return internalRequest.Response;
}
}
class InternalRequest : Request
{
InternalResponse response;
public override string ROOT => "";
public InternalRequest(Request baseRequest,string path)
:base(baseRequest.ApplicationServer)
{
Application = baseRequest.Application;
response = new InternalResponse(this);
Path = path;
}
public override Response Response => response;
public override MIMEMessage GetParameter(string name)
{
return null;
}
}
}

46
server/Response.cs 100644
View File

@ -0,0 +1,46 @@
using System;
using System.IO;
using System.Collections.Generic;
using appsrv.mime;
namespace appsrv.server
{
public abstract class Response
{
public Request Request { get; }
public int StatusCode { get; set; }
public MimeHeaders Headers { get; protected set; }
public Stream Stream { get; protected set; }
public TextWriter Writer { get; protected set; }
public object Reference { get; set; }
public byte[] ContentBytes => ((MemoryStream)Stream).ToArray();
private Dictionary<string, string> cookies = new Dictionary<string, string>();
public Response(Request request)
{
Request = request;
Headers = new MimeHeaders();
Stream = new MemoryStream();
Writer = new StreamWriter(Stream);
}
public void Flush()
{
Writer.Flush();
}
}
class InternalResponse : Response
{
public InternalResponse(Request request)
:base(request)
{
}
}
}

View File

@ -0,0 +1,80 @@
using System;
using appsrv.server;
using System.Collections.Generic;
namespace appsrv.sessions
{
public class Session
{
public ApplicationServer ApplicationServer { get; }
public Guid ID { get; }
public DateTime Created { get; }
public DateTime LastUsage { get; protected set; }
public bool TimedOut => (DateTime.Now - LastUsage) > TimeSpan.FromSeconds(1200);
Dictionary<string, object> storage = new Dictionary<string, object>();
Dictionary<Type, Dictionary<string, object>> typedStorage = new Dictionary<Type, Dictionary<string, object>>();
public Session(ApplicationServer applicationServer)
{
ApplicationServer = applicationServer;
Created = DateTime.Now;
LastUsage = DateTime.Now;
ID = Guid.NewGuid();
}
public object this[string key]
{
get => Get(key);
set => Set(key, value);
}
public void Touch()
{
LastUsage = DateTime.Now;
ApplicationServer.AddSession(this);
}
public object Get(string key)
{
if (storage.ContainsKey(key))
return storage[key];
return null;
}
public void Set(String key,object value)
{
storage[key] = value;
}
public bool ContainsKey(String key)
{
return storage.ContainsKey(key);
}
public bool ContainsKey<T>(String key)
{
return (typedStorage.ContainsKey(typeof(T))) && typedStorage[typeof(T)].ContainsKey(key);
}
public T Get<T>(string key)
{
if (typedStorage.ContainsKey(typeof(T)))
{
if (typedStorage[typeof(T)].ContainsKey(key))
return (T)typedStorage[typeof(T)][key];
}
throw new KeyNotFoundException();
}
public void Set<T>(String key,T value)
{
if (!typedStorage.ContainsKey(typeof(T)))
{
typedStorage.Add(typeof(T), new Dictionary<string, object>());
}
typedStorage[typeof(T)][key] = value;
}
}
}

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{FD508FE5-5879-4C60-91D8-CA408E06361F}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>appsrv</RootNamespace>
<AssemblyName>appsrv</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="connector\Http.cs" />
<Compile Include="server\ApplicationServer.cs" />
<Compile Include="connector\Connector.cs" />
<Compile Include="server\Application.cs" />
<Compile Include="exceptions\IllegalRequestException.cs" />
<Compile Include="resources\Resource.cs" />
<Compile Include="resources\FileResource.cs" />
<Compile Include="resources\DirectoryResource.cs" />
<Compile Include="exceptions\ApplicationServerException.cs" />
<Compile Include="exceptions\ResourceNotFoundException.cs" />
<Compile Include="mime\MimeHelper.cs" />
<Compile Include="http\HttpStatusCodes.cs" />
<Compile Include="resources\ResourceLink.cs" />
<Compile Include="attributes\WebCallableAttribute.cs" />
<Compile Include="test\StaticTest.cs" />
<Compile Include="http\QueryStringParameters.cs" />
<Compile Include="templates\Template.cs" />
<Compile Include="templates\FormElement.cs" />
<Compile Include="http\HttpRequest.cs" />
<Compile Include="templates\FormContext.cs" />
<Compile Include="templates\elements\Head.cs" />
<Compile Include="templates\elements\Var.cs" />
<Compile Include="templates\elements\Set.cs" />
<Compile Include="templates\elements\Iterate.cs" />
<Compile Include="templates\elements\If.cs" />
<Compile Include="templates\elements\Include.cs" />
<Compile Include="http\HtmlResolver.cs" />
<Compile Include="http\HtmlReader.cs" />
<Compile Include="templates\elements\Entity.cs" />
<Compile Include="templates\elements\Text.cs" />
<Compile Include="templates\token\Token.cs" />
<Compile Include="templates\token\PathToken.cs" />
<Compile Include="templates\token\KeywordToken.cs" />
<Compile Include="templates\elements\Framed.cs" />
<Compile Include="templates\Scanner.cs" />
<Compile Include="templates\token\NumberToken.cs" />
<Compile Include="templates\token\OperatorToken.cs" />
<Compile Include="streams\PeekableStream.cs" />
<Compile Include="streams\CharStream.cs" />
<Compile Include="streams\TokenStream.cs" />
<Compile Include="templates\token\ObjectPathToken.cs" />
<Compile Include="templates\token\CallToken.cs" />
<Compile Include="sessions\Session.cs" />
<Compile Include="security\SecureUser.cs" />
<Compile Include="security\Group.cs" />
<Compile Include="security\SecurityEntity.cs" />
<Compile Include="security\ISecurityProvider.cs" />
<Compile Include="security\UserInfo.cs" />
<Compile Include="security\AccessMask.cs" />
<Compile Include="attributes\PublishedMember.cs" />
<Compile Include="resources\reflection\ReflectiveResourceCrawler.cs" />
<Compile Include="resources\reflection\ReflectiveResource.cs" />
<Compile Include="test\RTest.cs" />
<Compile Include="templates\editors\HTMLEditor.cs" />
<Compile Include="templates\editors\StringEditor.cs" />
<Compile Include="templates\elements\Editor.cs" />
<Compile Include="templates\editors\IntegerEditor.cs" />
<Compile Include="templates\editors\DoubleEditor.cs" />
<Compile Include="mime\MimeReader.cs" />
<Compile Include="mime\MimeHeader.cs" />
<Compile Include="mime\MIMEMessage.cs" />
<Compile Include="mime\MIMEHeaders.cs" />
<Compile Include="mime\MimeType.cs" />
<Compile Include="mime\MimeStream.cs" />
<Compile Include="mime\StreamExtensions.cs" />
<Compile Include="mime\ArrayExtensions.cs" />
<Compile Include="http\StringExtensions.cs" />
<Compile Include="server\Request.cs" />
<Compile Include="server\Response.cs" />
<Compile Include="http\HttpResponse.cs" />
<Compile Include="templates\editors\MultiLineEditor.cs" />
<Compile Include="resources\reflection\ReflectiveResource.ReflectiveEditStore.cs" />
<Compile Include="resources\reflection\ReflectiveResource.ReflectiveEdit.cs" />
<Compile Include="attributes\HTMLEditorAttribute.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="connector\" />
<Folder Include="server\" />
<Folder Include="exceptions\" />
<Folder Include="resources\" />
<Folder Include="mime\" />
<Folder Include="http\" />
<Folder Include="attributes\" />
<Folder Include="test\" />
<Folder Include="test\www\" />
<Folder Include="test\form\" />
<Folder Include="test\www\static\" />
<Folder Include="templates\" />
<Folder Include="templates\elements\" />
<Folder Include="templates\token\" />
<Folder Include="templates\expressions\" />
<Folder Include="streams\" />
<Folder Include="sessions\" />
<Folder Include="security\" />
<Folder Include="resources\reflection\" />
<Folder Include="test\rtest.templates\" />
<Folder Include="templates\editors\" />
</ItemGroup>
<ItemGroup>
<None Include="test\TestApplication.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="test\www\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
<None Include="test\www\scripts.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="test\form\index.hfrm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="test\www\static\scripts.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="test\form\frame.hfrm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="test\www\static\style.css" />
<None Include="test\rtest.templates\appsrv.test.AClass.hfrm">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\sharp-logging\sharp.logging.csproj">
<Project>{D471A566-9FB6-41B2-A777-3C32874ECD0E}</Project>
<Name>sharp.logging</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<MonoDevelop>
<Properties>
<Policies>
<DotNetNamingPolicy ResourceNamePolicy="FileFormatDefault" DirectoryNamespaceAssociation="PrefixedHierarchical" />
</Policies>
</Properties>
</MonoDevelop>
</ProjectExtensions>
</Project>

View File

@ -0,0 +1,20 @@
using System;
using System.Text;
namespace appsrv.streams
{
public class CharStream : PeekAbleStream<char>
{
public CharStream(char[] buffer)
:base(buffer)
{
}
public CharStream(byte[] byteBuffer, Encoding encoding)
: this(encoding.GetChars(byteBuffer))
{
}
public CharStream(byte[] byteBuffer)
:this(Encoding.UTF8.GetChars(byteBuffer))
{
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Text;
namespace appsrv.streams
{
public class PeekAbleStream<T>
{
private T[] buffer;
private int position;
public int Position => position;
public bool EndOfBuffer => (position >= buffer.Length);
public int MarkedPosition { get; set; }
public PeekAbleStream(T[] buffer)
{
this.buffer = buffer;
}
public T Read(){
if (position < buffer.Length)
return buffer[position++];
throw new IndexOutOfRangeException("Tried to read after end of buffer");
}
public T Peek(int shift = 0)
{
if ((position + shift) < buffer.Length)
return buffer[position + shift];
throw new IndexOutOfRangeException("Tried to peek after end of buffer");
}
public void Mark()
{
MarkedPosition = position;
}
public T[] GetMarkedIntervall()
{
T[] intervall = new T[position - MarkedPosition];
Array.Copy(buffer, MarkedPosition, intervall, 0, intervall.Length);
return intervall;
}
public int Remaining => buffer.Length - position;
}
}

View File

@ -0,0 +1,20 @@
using System;
using appsrv.templates.token;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace appsrv.streams
{
public class TokenStream : PeekAbleStream<Token>
{
public TokenStream(Token[] tokens)
:base(tokens)
{
}
public TokenStream(IEnumerable<Token> tokens)
:base(tokens.ToArray())
{
}
}
}

View File

@ -0,0 +1,256 @@
using System;
using appsrv.server;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using System.Linq;
using System.ComponentModel;
using appsrv.templates.token;
using appsrv.resources;
using System.Resources;
namespace appsrv.templates
{
public class FormContext
{
private Dictionary<string, object> vars = new Dictionary<string, object>();
protected MemoryStream contentStream, headStream;
public Template SharpForm { get; }
public Request Request { get; }
public Stream ContentStream => contentStream;
public TextWriter ContentWriter { get; protected set; }
public Stream HeadStream => headStream;
public TextWriter HeadWriter { get; protected set; }
public FormContext Parent { get; }
public bool UsesClonedVars { get; }
public bool IsRootContext => Parent == null;
public FormContext SubFrameContext { get; protected set; }
public FormContext(Template sharpForm,Request request,object o)
{
Parent = null;
SharpForm = sharpForm;
Request = request;
contentStream = new MemoryStream();
ContentWriter = new StreamWriter(contentStream);
headStream = new MemoryStream();
HeadWriter = new StreamWriter(headStream);
vars["o"] = o;
vars["this"] = o;
vars["StrMax"] = GetType().GetMethod("StrMax");
}
public FormContext(FormContext parent,bool cloneVars = false,bool independentContent = false,FormContext subFrameContext = null)
{
Parent = parent;
SharpForm = parent.SharpForm;
Request = parent.Request;
contentStream = independentContent ? new MemoryStream() : parent.contentStream;
ContentWriter = new StreamWriter(ContentStream);
headStream = parent.headStream;
HeadWriter = parent.HeadWriter;
UsesClonedVars = cloneVars;
SubFrameContext = subFrameContext == null ? parent.SubFrameContext : subFrameContext;
if (cloneVars){
foreach (KeyValuePair<string,object> var in parent.vars)
{
this.vars.Add(var.Key, var.Value);
}
}
parent.ContentWriter.Flush();
}
public byte[] ContentBytes
{
get
{
ContentWriter.Flush();
return contentStream.ToArray();
}
}
public byte[] HeadBytes
{
get
{
HeadWriter.Flush();
return headStream.ToArray();
}
}
public String Content => Encoding.UTF8.GetString(ContentBytes);
public String Head => Encoding.UTF8.GetString(HeadBytes);
public void Insert(FormContext context, bool allToHead = false)
{
if (context.headStream != headStream)
{
HeadWriter.Flush();
byte[] headBytes = context.HeadBytes;
headStream.Write(headBytes, 0, headBytes.Length);
}
ContentWriter.Flush();
byte[] contentBytes = context.ContentBytes;
if (allToHead)
headStream.Write(contentBytes, 0, contentBytes.Length);
else
contentStream.Write(contentBytes, 0, contentBytes.Length);
}
public bool HasVar(String name)
{
if (vars.ContainsKey(name))
return true;
if (!UsesClonedVars && (Parent != null))
return Parent.HasVar(name);
return false;
}
public object Get(String name){
if (vars.ContainsKey(name))
return vars[name];
else if (Parent != null)
return Parent.Get(name);
else
return null;
}
public void Set(String name,object value){
if (!UsesClonedVars && (Parent != null) && (Parent.HasVar(name)))
{
Parent.Set(name, value);
} else {
this.vars[name] = value;
}
}
public object EvaluateExpression(Token[] tokens)
{
if (tokens.Length == 0)
{
return "";
}
Stack<Token> tokenStack = new Stack<Token>(tokens);
object currentValue = null;
Token currentToken = null;
while (tokenStack.Count > 0){
currentToken = tokenStack.Pop();
if (currentToken is PathToken)
{
PathToken pathToken = (PathToken)currentToken;
Response response = Request.SubRequest(pathToken.Path);
if (response.Reference != null)
currentValue = response.Reference;
else
currentValue = Encoding.UTF8.GetString(response.ContentBytes);
} else if (currentToken is KeywordToken)
{
KeywordToken keywordToken = (KeywordToken)currentToken;
currentValue = keywordToken.Evaluate(this);
}
if (tokenStack.Count > 0){
currentToken = tokenStack.Pop();
throw new NotImplementedException("Currently no expression operators are implemented, sorry");
}
}
return currentValue;
}
public object EvaluateValue(String sValue)
{
Stack<String> vpath = new Stack<string>(sValue.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).Reverse());
if (vpath.Count > 0)
{
object value = null;
string key = vpath.Pop();
value = Get(key);
while (vpath.Count > 0)
{
key = vpath.Pop();
value = GetObjectFieldOrProperty(value, key);
}
return value;
}
return null;
}
public bool EvaluateBoolean(String expression)
{
bool invert = (expression[0] == '!');
if (invert)
expression = expression.Substring(1);
object value = EvaluateValue(expression);
if (value != null)
{
if (
((value is bool) && (((bool)value))) ||
((value is string) && (!String.Empty.Equals(value))) ||
((value is int) && (((int)value) != 0)) ||
((value is long) && (((long)value) != 0)) ||
((value is float) && (((float)value) != 0)) ||
((value is double) && (((double)value) != 0))
)
{
return !invert;
}
}
return invert;
}
private object GetObjectFieldOrProperty(object o, string name)
{
if (o == null)
{
return null;
}
FieldInfo fieldInfo = o.GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (fieldInfo != null)
{
return fieldInfo.GetValue(o);
}
PropertyInfo propertyInfo = o.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (propertyInfo != null)
{
return propertyInfo.GetValue(o);
}
throw new KeyNotFoundException(String.Format("object of type {0} has no field/property named {1}", o.GetType().FullName, name));
}
public static string StrMax(string s,int len,string suffix = "")
{
if (len < s.Length)
return s.Substring(0, len) + suffix;
return s;
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Xml;
using System.Collections.Generic;
using appsrv.server;
using appsrv.templates.elements;
namespace appsrv.templates
{
public abstract class FormElement
{
/* Instance Members */
public FormElement Container { get; }
List<FormElement> children = new List<FormElement>();
public FormElement(FormElement container)
{
Container = container;
if (container != null)
{
container.children.Add(this);
}
}
public abstract void Run(FormContext context);
public FormElement[] Children => children.ToArray();
public virtual Template Form => Container.Form;
public void RunChildren(FormContext context)
{
foreach (FormElement child in children)
{
child.Run(context);
}
}
}
}

View File

@ -0,0 +1,297 @@
using System;
using System.Linq;
using appsrv.templates.token;
using System.Collections.Generic;
using System.Text;
using appsrv.templates.elements;
using appsrv.streams;
using System.Runtime.InteropServices;
namespace appsrv.templates
{
/**
*
* Expression Syntax
*
* expr := ( <constant> | ( <objpath> | <path> ) [ "(" <arguments> ")" ] )
* objpath := <keyword> [ "." <objpath> ]
* arguments := <expr> [ "," <arguments> ]
*
*
**/
public static class Scanner
{
private static char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
private static char[] digits = "0123456789".ToCharArray();
private static char[] keywordChars = letters.Concat(digits).Concat(new char[] { '_' }).ToArray();
private static char[] pathChars = keywordChars.Concat(new char[] { '.','/' }).ToArray();
private static char[] numberChars = digits.Concat(new char[] { '.' }).ToArray();
private static char[] operatorChars = new char[] { '+', '-', '*', '/' };
private static char[] ScanValidCharacters(CharStream charStream,char[] validCharacters)
{
charStream.Mark();
while (!charStream.EndOfBuffer && validCharacters.Contains(charStream.Peek())){
charStream.Read();
}
return charStream.GetMarkedIntervall();
}
public static String ScanKeyword(CharStream charStream)
{
if (!Char.IsLetter(charStream.Peek()))
throw new ArgumentException(String.Format("ScanKeyword positioned on character {0}", charStream.Peek()));
return new string(ScanValidCharacters(charStream,keywordChars));
}
public static String ScanNumber(CharStream charStream)
{
if (!Char.IsDigit(charStream.Peek()))
throw new ArgumentException(String.Format("ScanNumber positioned on character {0}", charStream.Peek()));
return new string(ScanValidCharacters(charStream, numberChars));
}
public static string ScanString(CharStream charStream)
{
if (charStream.Peek() != '"')
throw new ArgumentException(String.Format("ScanString positioned on {0}", charStream.Peek()));
charStream.Read();
charStream.Mark();
while (charStream.Peek() != '"')
{
if (charStream.Read() == '\\')
{
charStream.Read();
}
}
string result = new string(charStream.GetMarkedIntervall());
charStream.Read();
return result;
}
public static PathToken ScanPath(CharStream charStream)
{
if ((charStream.Peek() != '/')&&(charStream.Peek() != '.'))
throw new ArgumentException(String.Format("ScanPath positioned on character {0}", charStream.Peek()));
if (charStream.Peek() == '.')
{
charStream.Read();
charStream.Read();
}
return new PathToken(new string(ScanValidCharacters(charStream, pathChars)));
}
public static Token ScanObjectPath(CharStream charStream,ObjectPathToken parent = null){
if (!Char.IsLetter(charStream.Peek()))
throw new ArgumentOutOfRangeException(String.Format("ScanObjectPath is not positioned on a letter: {0}",charStream.Peek()));
String component = ScanKeyword(charStream);
if (charStream.Peek() == '.')
{
charStream.Read();
return ScanObjectPath(charStream, new ObjectPathToken(component, parent));
} else if (charStream.Peek() == '(')
{
Token[] arguments = ScanArguments(charStream);
return new CallToken(component, arguments, parent);
} else {
return new ObjectPathToken(component, parent);
}
}
public static char[] SeekProcessingInstruction(CharStream charStream)
{
charStream.Mark();
while (!charStream.EndOfBuffer)
{
if ((charStream.Remaining > 1) && (charStream.Peek(0) == '<') && (charStream.Peek(1) == '?'))
break;
charStream.Read();
}
return charStream.GetMarkedIntervall();
}
public static Token ScanExpression(CharStream charStream)
{
while (!charStream.EndOfBuffer)
{
if (charStream.Peek() == '"')
{
return new ValueToken(ScanString(charStream));
}
else if (Char.IsLetter(charStream.Peek()))
{
return ScanObjectPath(charStream);
}
else if ((charStream.Peek() == '/')||(charStream.Peek() == '.'))
{
return ScanPath(charStream);
}
else if (Char.IsDigit(charStream.Peek()))
{
return new NumberToken(ScanNumber(charStream));
}
else
{
Console.WriteLine("Unexpected character: {0}", charStream.Read());
}
}
return null;
}
public static Token[] ScanArguments(CharStream charStream){
if (charStream.Peek() != '(')
throw new ArgumentException(String.Format("ScanArguments not positioned on ("));
charStream.Read();
List<Token> arguments = new List<Token>();
while (!charStream.EndOfBuffer && (charStream.Peek() != ')'))
{
if (Char.IsWhiteSpace(charStream.Peek()))
{
charStream.Read();
} else {
arguments.Add(ScanExpression(charStream));
while (Char.IsWhiteSpace(charStream.Peek()))
{
charStream.Read();
}
if (charStream.Peek() == ')')
{
break;
}
if (charStream.Peek() != ',')
throw new ArgumentException(String.Format("expected , or ) after argument"));
}
}
charStream.Read();
return arguments.ToArray();
}
public static Token[] ScanParameters(CharStream charStream)
{
List<Token> tokens = new List<Token>();
while (!charStream.EndOfBuffer)
{
if ((charStream.Remaining > 1) && (charStream.Peek() == '?') && (charStream.Peek(1) == '>'))
{
charStream.Read();
charStream.Read();
break;
}
else if (Char.IsWhiteSpace(charStream.Peek()))
{
charStream.Read();
} else {
tokens.Add(ScanExpression(charStream));
}
}
return tokens.ToArray();
}
public static void Scan(CharStream sourceStream, FormElement container)
{
while (!sourceStream.EndOfBuffer)
{
char[] textPart = SeekProcessingInstruction(sourceStream);
if (textPart.Length > 0)
{
new Text(container, textPart);
}
if (!sourceStream.EndOfBuffer)
{
if (!ScanProcessingInstruction(sourceStream, container))
{
return;
}
}
}
}
public static bool ScanProcessingInstruction(CharStream sourceStream, FormElement container)
{
if ((sourceStream.Remaining > 1) && (sourceStream.Peek(0) == '<') && (sourceStream.Peek(1) == '?'))
{
sourceStream.Read();
sourceStream.Read();
if (sourceStream.Peek() == '=')
{
sourceStream.Read();
new Var(container, ScanParameters(sourceStream));
}
else
{
String keyword = ScanKeyword(sourceStream);
Token[] parameters = ScanParameters(sourceStream);
switch (keyword)
{
case "include":
new Include(container, parameters);
break;
case "head":
Scan(sourceStream, new Head(container));
break;
case "set":
new Set(container, ((ObjectPathToken)parameters[0]).Component ,parameters[1]);
break;
case "iterate":
Scan(sourceStream, new Iterate(container,((ObjectPathToken)parameters[0]).Component,parameters[1]));
break;
case "if":
Scan(sourceStream, new If(container, parameters[0]));
break;
case "editor":
new Editor(container, parameters);
break;
case "frame":
if (!(parameters[0] is PathToken))
throw new ArgumentException("<?frame ...?> needs path as argument.");
container.Form.Frame = (PathToken)parameters[0];
break;
case "framed":
new Framed(container);
break;
case "else":
if (container is ConditionalFormElement)
{
ConditionalFormElement conditionalFormElement = container as ConditionalFormElement;
Scan(sourceStream, conditionalFormElement.AlternativeElement);
}
break;
case "end":
return false;
}
}
return true;
}
else
{
throw new ArgumentException("ScanProcessingInstruction was not positioned on PI");
}
}
}
}

View File

@ -0,0 +1,163 @@
using System;
using System.Xml;
using appsrv.resources;
using System.IO;
using appsrv.server;
using System.Collections.Generic;
using appsrv.exceptions;
using appsrv.http;
using System.Text;
using appsrv.templates.elements;
using Newtonsoft.Json.Serialization;
using appsrv.templates.token;
using appsrv.streams;
namespace appsrv.templates
{
public class Template : Resource
{
String sourceFilename;
public Resource LookupBase { get; set; }
public PathToken Frame { get; set; }
public String Title { get; set; }
public SharpFormElement RootElement { get; protected set; }
public Template(String filename,Resource container)
:base(new FileInfo(filename).Name,container)
{
sourceFilename = filename;
LookupBase = container.Root;
LoadSource();
}
protected override void Add(Resource resource)
{
throw new NotImplementedException("");
}
private void LoadSource()
{
byte[] loadBuffer;
RootElement = new SharpFormElement(this);
using (FileStream fileStream = new FileStream(sourceFilename,FileMode.Open))
{
loadBuffer = new byte[fileStream.Length];
fileStream.Read(loadBuffer, 0, loadBuffer.Length);
fileStream.Close();
}
CharStream sourceStream = new CharStream(loadBuffer);
Scanner.Scan(sourceStream, RootElement);
LastModification = File.GetLastWriteTime(sourceFilename);
}
public bool CheckReloadNeeded()
{
bool reloadNeeded = (File.GetLastWriteTime(sourceFilename) > LastModification);
if (reloadNeeded)
{
LoadSource();
}
return reloadNeeded;
}
public FormContext Render(FormContext context){
CheckReloadNeeded();
RootElement.Run(context);
if (Frame != null)
{
FormContext frameContext = new FormContext(context, independentContent: true, subFrameContext: context);
Template frame = (Template)context.SharpForm.FindByPath(Frame.Path);
return frame.Render(frameContext);
}
return context;
}
public override void Hit(Stack<string> requestPath, Request request)
{
object o = null;
/* if (requestPath.Count > 0)
{
Response response = request.SubRequest(requestPath);
if (response.HasValue)
o = response.Value;
else
o = response.Content;
}*/
Render(request, o);
}
public void Render(Request request,object o,IEnumerable<KeyValuePair<string,object>> keyValues = null)
{
FormContext renderContext = new FormContext(this, request, o);
if (keyValues != null)
{
foreach (KeyValuePair<string,object> kvp in keyValues)
{
renderContext.Set(kvp.Key,kvp.Value);
}
}
renderContext.Set("request", request);
FormContext context = Render(renderContext);
Response response = request.Response;
response.Headers.Add("Content-type", "text/html;charset=utf-8");
response.Writer.WriteLine("<!DOCTYPE html>");
response.Writer.WriteLine("<html><head>");
response.Writer.Flush();
byte[] headBytes = context.HeadBytes;
response.Stream.Write(headBytes,0,headBytes.Length);
response.Writer.WriteLine("</head><body>");
response.Writer.Flush();
byte[] contentBytes = context.ContentBytes;
response.Stream.Write(contentBytes, 0, contentBytes.Length);
response.Writer.WriteLine("</body></html>");
response.Writer.Flush();
}
public class SharpFormElement : FormElement
{
Template sharpForm;
public override Template Form => sharpForm;
public SharpFormElement(Template sharpForm) : base(null)
{
this.sharpForm = sharpForm;
}
public override void Run(FormContext context)
{
foreach (FormElement formElement in Children)
{
formElement.Run(context);
}
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Security;
using appsrv.templates.token;
using System.Globalization;
namespace appsrv.templates.editors
{
public class DoubleEditor: HTMLEditor
{
public DoubleEditor(Template template, ObjectPathToken objectPathToken)
:base(template,objectPathToken)
{
}
public override void RenderHTML(FormContext context)
{
context.ContentWriter.Write("<input type=\"number\" class=\"editor number\" name=\"{0}\" step=\"any\" value=\"{1}\"/>",
ObjectPathToken.ToString().Replace('.', '_'),
GetHTMLValue(context)
);
}
public override string GetHTMLValue(FormContext context)
{
double v = (double)GetValue(context);
return v.ToString("G",CultureInfo.InvariantCulture);
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using appsrv.templates.token;
using System.Linq;
using System.Collections.Generic;
using appsrv.attributes;
namespace appsrv.templates.editors
{
public delegate HTMLEditor HTMLEditorFactory(FormContext context, ObjectPathToken pathToken, Token[] parameters);
public abstract class HTMLEditor
{
static List<HTMLEditorFactory> customEditors = new List<HTMLEditorFactory>();
static Type[] IntTypes = new Type[]{
typeof(int),
typeof(Int16),
typeof(Int64),
typeof(uint),
typeof(UInt16),
typeof(UInt64)
};
static Type[] FloatTypes = new Type[]{
typeof(float),
typeof(double)
};
public static void RegisterFactory(HTMLEditorFactory factory)
{
customEditors.Add(factory);
}
public static HTMLEditor Construct(FormContext context,ObjectPathToken pathToken,Token[] parameters)
{
HTMLEditorAttribute htmlEditorAttribute = pathToken.GetCustomAttribute<HTMLEditorAttribute>(context);
Type valueType = pathToken.GetTargetType(context);
HTMLEditor editor = null;
foreach (HTMLEditorFactory factory in customEditors)
{
editor = factory(context, pathToken, parameters);
if (editor != null)
return editor;
}
if (IntTypes.Contains(valueType))
return new IntegerEditor(context.SharpForm, pathToken);
if (FloatTypes.Contains(valueType))
return new DoubleEditor(context.SharpForm, pathToken);
return new StringEditor(context.SharpForm, pathToken);
}
public Template Template { get; }
public ObjectPathToken ObjectPathToken { get; }
public HTMLEditor(Template template,ObjectPathToken objectPathToken)
{
Template = template;
ObjectPathToken = objectPathToken;
}
public abstract void RenderHTML(FormContext context);
public virtual object GetValue(FormContext context){
return ObjectPathToken.Evaluate(context);
}
public virtual String GetHTMLValue(FormContext context)
{
object v = GetValue(context);
if (v == null)
return "";
return v.ToString();
}
public virtual void SetValue(FormContext context,object value){
ObjectPathToken.SetValue(context, value);
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Security;
using appsrv.templates.token;
namespace appsrv.templates.editors
{
public class IntegerEditor: HTMLEditor
{
public IntegerEditor(Template template, ObjectPathToken objectPathToken)
:base(template,objectPathToken)
{
}
public override void RenderHTML(FormContext context)
{
context.ContentWriter.Write("<input type=\"number\" class=\"editor number\" name=\"{0}\" step=\"1\" value=\"{1}\"/>",
ObjectPathToken.ToString().Replace('.', '_'),
GetHTMLValue(context)
);
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Security;
using appsrv.templates.token;
namespace appsrv.templates.editors
{
public class MultiLineEditor: HTMLEditor
{
public MultiLineEditor(Template template, ObjectPathToken objectPathToken)
:base(template,objectPathToken)
{
}
public override void RenderHTML(FormContext context)
{
context.ContentWriter.Write("<textarea class=\"editor string\" name=\"{0}\">{1}</textarea>",
ObjectPathToken.ToString().Replace('.', '_'),
GetHTMLValue(context)
);
}
public override string GetHTMLValue(FormContext context)
{
return SecurityElement.Escape(base.GetHTMLValue(context));
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Security;
using appsrv.templates.token;
namespace appsrv.templates.editors
{
public class StringEditor : HTMLEditor
{
public StringEditor(Template template, ObjectPathToken objectPathToken)
:base(template,objectPathToken)
{
}
public override void RenderHTML(FormContext context)
{
context.ContentWriter.Write("<input type=\"text\" class=\"editor string\" name=\"{0}\" value=\"{1}\"/>",
ObjectPathToken.ToString().Replace('.', '_'),
GetHTMLValue(context)
);
}
public override string GetHTMLValue(FormContext context)
{
return SecurityElement.Escape(base.GetHTMLValue(context));
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Xml;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using appsrv.templates.token;
using appsrv.templates.editors;
using appsrv.attributes;
namespace appsrv.templates.elements
{
public class Editor : FormElement
{
public ObjectPathToken PathToken { get; }
public Token[] Parameters { get; }
public HTMLEditor HTMLEditor { get; protected set; }
public Token EditorName { get; protected set; }
public Editor(FormElement container,Token[] tokens)
:base(container)
{
PathToken = (ObjectPathToken)tokens[0];
Parameters = tokens.Skip(1).ToArray();
}
public override void Run(FormContext context)
{
if (HTMLEditor == null)
{
HTMLEditor = HTMLEditor.Construct(context, PathToken, Parameters);
}
HTMLEditor.RenderHTML(context);
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Xml;
namespace appsrv.templates.elements
{
public class Entity : FormElement
{
public String EntityName { get; }
public Entity(FormElement container,XmlNode xmlNode)
:base(container)
{
EntityName = xmlNode.Name;
}
public override void Run(FormContext context)
{
context.ContentWriter.Write("&{0}",EntityName);
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace appsrv.templates.elements
{
public class Framed : FormElement
{
public Framed(FormElement container)
:base(container)
{
}
public override void Run(FormContext context)
{
if (context.SubFrameContext != null){
context.Insert(context.SubFrameContext);
}
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Xml;
namespace appsrv.templates.elements
{
public class Head : FormElement
{
public Head(FormElement container)
:base(container)
{
}
public override void Run(FormContext context)
{
FormContext headContext = new FormContext(context, independentContent: true);
RunChildren(headContext);
context.Insert(headContext,true);
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Xml;
using appsrv.templates.token;
namespace appsrv.templates.elements
{
public abstract class ConditionalFormElement : FormElement
{
public FormElement AlternativeElement { get; }
public ConditionalFormElement(FormElement container)
:base(container)
{
AlternativeElement = new AlternativeFormElement(this);
}
public class AlternativeFormElement : FormElement
{
public AlternativeFormElement(ConditionalFormElement container)
:base(container.Container)
{
}
public override void Run(FormContext context)
{
}
}
}
public class If : ConditionalFormElement
{
public Token Expression { get; }
public If(FormElement container,Token expression)
:base(container)
{
Expression = expression;
}
public bool EvaluateExpression(FormContext context){
object result = Expression.Evaluate(context);
if (result == null)
return false;
if ((result is int) && ((int)result == 0))
return false;
if ((result is float) && ((float)result == 0.0))
return false;
if ((result is bool) && ((bool)result == false))
return false;
return true;
}
public override void Run(FormContext context)
{
if (EvaluateExpression(context))
RunChildren(context);
else
AlternativeElement.RunChildren(context);
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Xml;
using appsrv.resources;
using System.Threading;
using appsrv.templates.token;
using System.Runtime.Remoting.Contexts;
namespace appsrv.templates.elements
{
public class Include : FormElement
{
public PathToken Path { get; }
public Token OToken { get; }
public Include(FormElement container,Token[] parameters)
:base(container)
{
Path = (PathToken)parameters[0];
if (parameters.Length > 1)
{
OToken = parameters[1];
}
}
public override void Run(FormContext context)
{
Resource frameResource = (Resource)context.SharpForm.Root.FindByPath(Path.Path);
if (frameResource is Template)
{
Template frame = (Template)frameResource;
FormContext frameContext = new FormContext(context);
frame.Render(frameContext);
frameContext.HeadWriter.Flush();
frameContext.ContentWriter.Flush();
}
else
{
throw new ArgumentException(String.Format("Path {0} is not a SharpForm", Path.Path));
}
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Xml;
using System.Collections.Generic;
using System.Collections;
using appsrv.streams;
using appsrv.templates.token;
namespace appsrv.templates.elements
{
public class Iterate : FormElement
{
public String Key { get; }
public Token Expression { get; }
public Iterate(FormElement container,String key,Token expression)
:base(container)
{
Key = key;
Expression = expression;
}
public override void Run(FormContext context)
{
FormContext iterateContext = new FormContext(context);
object iteration = Expression.Evaluate(context);
if (iteration is IEnumerable){
IEnumerable enumerable = (IEnumerable)iteration;
IEnumerator enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
iterateContext.Set(Key, enumerator.Current);
RunChildren(iterateContext);
}
iterateContext.ContentWriter.Flush();
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Xml;
using System.Text;
using appsrv.templates.token;
namespace appsrv.templates.elements
{
public class Set : FormElement
{
public String Target { get; }
public Token Expression { get; }
public Set(FormElement container,String target,Token expression)
:base(container)
{
Target = target;
Expression = expression;
}
public override void Run(FormContext context)
{
context.Set(Target, Expression.Evaluate(context));
}
}
}

View File

@ -0,0 +1,21 @@
using System;
namespace appsrv.templates.elements
{
public class Text : FormElement
{
private char[] chars;
public Text(FormElement container,char[] text)
:base(container)
{
this.chars = text;
}
public override void Run(FormContext context)
{
context.ContentWriter.Write(this.chars);
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Xml;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using appsrv.templates.token;
namespace appsrv.templates.elements
{
public class Var : FormElement
{
Token[] tokens;
public Var(FormElement container,Token[] tokens)
:base(container)
{
this.tokens = tokens;
}
public override void Run(FormContext context)
{
foreach (Token token in tokens)
{
object o = token.Evaluate(context);
if (o != null)
o = o.ToString();
context.ContentWriter.Write(
System.Net.WebUtility.HtmlEncode(o as string)
);
}
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Reflection;
namespace appsrv.templates.token
{
public class CallToken : Token
{
public Token Owner { get; }
public String Method { get; }
public Token[] Arguments { get; }
public CallToken(String method,Token[] arguments,Token owner)
{
Owner = owner;
Method = method;
Arguments = arguments;
}
public override object Evaluate(FormContext context)
{
object owner;
Type ownerType;
if (Owner != null)
{
owner = Owner.Evaluate(context);
ownerType = owner.GetType();
} else {
MethodInfo methodInfo = context.Get(Method) as MethodInfo;
owner = null;
ownerType = methodInfo.DeclaringType;
}
Type[] types = new Type[Arguments.Length];
object[] arguments = new object[Arguments.Length];
for (int n = 0; n < arguments.Length;n++)
{
arguments[n] = Arguments[n].Evaluate(context);
}
return ownerType.InvokeMember(Method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, owner, arguments);
}
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace appsrv.templates.token
{
public class KeywordToken : Token
{
public String Value { get; }
public KeywordToken(string value)
{
Value = value;
}
public override object Evaluate(FormContext context)
{
return context.EvaluateValue(Value);
}
public override string ToString()
{
return String.Format("[KeywordToken Value={0}]", Value);
}
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace appsrv.templates.token
{
public class NumberToken : ValueToken
{
public NumberToken(String value)
{
if (value.IndexOf('.') != -1)
Value = Double.Parse(value);
else
Value = int.Parse(value);
}
}
}

View File

@ -0,0 +1,136 @@
using System;
using System.Reflection;
namespace appsrv.templates.token
{
public class ObjectPathToken : Token
{
public ObjectPathToken ParentToken { get; }
public String Component { get; }
public ObjectPathToken(String component,ObjectPathToken parent)
{
Component = component;
ParentToken = parent;
}
public virtual void SetValue(FormContext context,object value)
{
if (ParentToken == null)
{
context.Set(Component, value);
} else {
object parent = ParentToken.Evaluate(context);
if (parent == null)
throw new NullReferenceException();
Type type = parent.GetType();
FieldInfo fieldInfo = type.GetField(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (fieldInfo != null)
{
if (fieldInfo.FieldType != value.GetType())
value = Convert.ChangeType(value, fieldInfo.FieldType);
fieldInfo.SetValue(parent,value);
}
PropertyInfo propertyInfo = type.GetProperty(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (propertyInfo != null)
{
if (propertyInfo.PropertyType != value.GetType())
value = Convert.ChangeType(value, propertyInfo.PropertyType);
propertyInfo.SetValue(parent,value);
}
throw new MissingMemberException(String.Format("object member not found {0} {1}", Component, parent));
}
}
public virtual object GetValue(FormContext context)
{
object parent = null;
if (ParentToken == null)
{
return context.Get(Component);
} else {
parent = ParentToken.Evaluate(context);
}
if (parent == null)
throw new NullReferenceException();
Type type = parent.GetType();
FieldInfo fieldInfo = type.GetField(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (fieldInfo != null)
{
return fieldInfo.GetValue(parent);
}
PropertyInfo propertyInfo = type.GetProperty(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (propertyInfo != null)
{
return propertyInfo.GetValue(parent);
}
throw new MissingMemberException(String.Format("object member not found {0} {1}",Component,parent));
}
public Type GetTargetType(FormContext context)
{
if (ParentToken == null)
{
return context.Get(Component).GetType();
}
Type parentType = ParentToken.GetTargetType(context);
FieldInfo fieldInfo = parentType.GetField(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (fieldInfo != null)
{
return fieldInfo.FieldType;
}
PropertyInfo propertyInfo = parentType.GetProperty(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (propertyInfo != null)
{
return propertyInfo.PropertyType;
}
throw new MissingMemberException(String.Format("object member not found {0} {1}", Component, parentType));
}
public T GetCustomAttribute<T>(FormContext context) where T: Attribute
{
Type parentType = (ParentToken == null) ? context.Get(Component).GetType() : ParentToken.GetTargetType(context);
FieldInfo fieldInfo = parentType.GetField(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (fieldInfo != null)
{
return fieldInfo.GetCustomAttribute<T>();
}
PropertyInfo propertyInfo = parentType.GetProperty(Component, BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (propertyInfo != null)
{
return propertyInfo.GetCustomAttribute<T>();
}
throw new MissingMemberException(String.Format("object member not found {0} {1}", Component, parentType));
}
public override object Evaluate(FormContext context) => GetValue(context);
public override string ToString()
{
if (ParentToken == null)
return Component;
return String.Format("{0}.{1}",ParentToken.ToString(),Component);
}
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace appsrv.templates.token
{
public class OperatorToken : Token
{
public char Operator { get; }
public OperatorToken(char op)
{
Operator = op;
}
public override object Evaluate(FormContext context)
{
return null;
}
public override string ToString()
{
return String.Format("[OperatorToken Operator={0}]", Operator);
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using appsrv.resources;
using System.Text;
using appsrv.server;
namespace appsrv.templates.token
{
public class PathToken : Token
{
public string Path { get; set; }
public PathToken(String path)
{
Path = path;
}
public override object Evaluate(FormContext context)
{
Response response = context.Request.SubRequest(Path);
response.Flush();
byte[] content = response.ContentBytes;
return Encoding.UTF8.GetString(content);
}
}
}

View File

@ -0,0 +1,37 @@
using System;
namespace appsrv.templates.token
{
public abstract class Token
{
public Token()
{
}
public abstract object Evaluate(FormContext context);
}
public class ValueToken : Token
{
public object Value { get; protected set; }
protected ValueToken()
{
}
public ValueToken(object value)
{
Value = value;
}
public override object Evaluate(FormContext context)
{
return Value;
}
public override string ToString()
{
return String.Format("[ValueToken Value={0}]", Value);
}
}
}

54
test/RTest.cs 100644
View File

@ -0,0 +1,54 @@
using System;
using appsrv.attributes;
namespace appsrv.test
{
public class RTest
{
[PublishedMember]
public static AClass CreateAClass(){
return new AClass();
}
}
public class AClass {
BClass bClass;
[PublishedMember]
public BClass PropB => bClass;
[PublishedMember]
public BClass GetB() { return bClass; }
[PublishedMember]
public BClass CreateB(String v){
return new BClass(this, v);
}
public AClass()
{
bClass = new BClass(this);
}
}
public class BClass {
[PublishedMember]
public AClass A;
[PublishedMember]
public string Version = "1.0.1234";
public BClass(AClass a)
{
A = a;
}
public BClass(AClass a,String v)
{
A = a;
Version = v;
}
}
}

View File

@ -1,21 +1,89 @@
using System;
using appsrv.attributes;
using System.Collections.Generic;
namespace appsrv.test
{
public class StaticTest
{
[WebCallable]
[WebCallableAttribute]
public static int Add(int a,int b){
Console.WriteLine("StaticTest.Add({0},{1})", a, b);
return a + b;
}
[WebCallable]
public static void Debug()
[WebCallableAttribute]
public static String Debug(String debugMessage = null)
{
Console.WriteLine("StaticTest.Debug() has been called");
String debugLine = String.Format("StaticTest.Debug({0}) has been called", debugMessage);
Console.WriteLine(debugLine);
return debugLine;
}
static string[] lookupItems = new string[]{
"Ich bin ein Mondstein",
"Du bist ein Saurier",
"Wir sind hier",
"Ihr seid irgendwo",
"Du warst gestern nicht hier",
"Ihr seid es heute auch nicht mehr",
"Warum sind wir nicht hier?"
};
[WebCallableAttribute(Serialization = SERIALIZATION.JSON)]
public static String[] LookupList(String pattern){
List<String> result = new List<string>();
foreach (String item in lookupItems){
if (item.StartsWith(pattern))
result.Add(item);
}
return result.ToArray();
}
[WebCallableAttribute]
public static Test TestInstance(){
return new Test();
}
static Random random = new Random();
[WebCallableAttribute]
public static int Random()
{
return random.Next();
}
[WebCallableAttribute]
public static int[] Loop(int loopLength = 1000)
{
int[] ia = new int[loopLength];
for (int n = 0; n < loopLength;n++)
{
ia[n] = n;
}
return ia;
}
public class Test
{
public static int[] Loops => Loop(1000);
public String Name { get; set; } = "Mein Name";
public int age = 18;
public String NickName { get; set; } = "Der Spitze";
string[] texts = lookupItems;
bool True = true;
bool False = false;
public Test()
{
}
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<SharpWebApplication>
<ApplicationName>localhost</ApplicationName>
<Resource Default="/index.html">
<Provider Name="StaticDirectory">www</Provider>
<Resource>
<Provider Name="StaticClass">appsrv.test.StaticTest</Provider>
</Resource>
<Resource>
<Provider Name="StaticDirectory">www/static</Provider>
</Resource>
<Resource Default="index.hfrm">
<Provider Name="StaticDirectory">form</Provider>
</Resource>
<Resource>
<Provider Name="ReflectiveResource">appsrv.test.RTest</Provider>
<Parameter Name="templates">rtest.templates</Parameter>
</Resource>
</Resource>
</SharpWebApplication>

View File

@ -0,0 +1,18 @@
<?head?>
<title>sharp-application-server Version [<?="0.0.0" ?>] / <?=pagetitle?></title>
<link rel="stylesheet" href="/style.css">
<?end?>
<section id="headbar">
<div class="title"><a id="wawi" href="/">WaWi</a></div>
<div class="title center" id="pagetitle"><?=pagetitle ?></div>
</section>
<section id="pagearea">
<?framed?>
</section>
<section id="footbar">
<div></div>
<div id="pagetime"><?="----"?></div>
</section>

View File

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<SharpForm Title="Index Form" Frame="/form/frame.hfrm">
<Set Name="title">Index Page</Set>
<div><Var>title</Var></div>
<br/>
<div>Object: <Var>o</Var></div>
<div>Name: <Var>o.Name</Var></div>
<div>Age: <Var>o.age</Var></div>
<Iterate Key="text" Expression="o.texts">
<div>Ein Text: <Var>text</Var></div>
</Iterate>
<h1>Boolean Conditionals Test</h1>
<If Expression="o.True"><h1>YES</h1></If>
<If Expression="!o.True"><h1>!YES</h1></If>
<If Expression="o.False"><h1>NO</h1></If>
<If Expression="!o.False"><h1>!NO</h1></If>
<table>
<thead>
<tr>
<td>#</td>
<td>Spalte 1</td>
<td>Spalte 2</td>
<td>Spalte 3</td>
<td>Spalte 4</td>
</tr>
</thead>
<Iterate Key="n" Expression="/StaticTest/Loop">
<tr>
<td>#<Var Expression="n"/></td>
<td><Var>/StaticTest/Random</Var></td>
<td><Var>/StaticTest/Random</Var></td>
<td><Var>/StaticTest/Random</Var></td>
<td><Var>/StaticTest/Random</Var></td>
</tr>
</Iterate>
</table>
</SharpForm>

View File

@ -0,0 +1,44 @@
<?frame /form/frame.hfrm?>
<?set pagetitle "AClass Form"?>
<div><?=pagetitle?></div>
<br/>
<div>Object: <?=o?></div>
<div>Name: <?=o?></div>
<div>Age: <?=o.PropB?></div>
<Iterate Key="text" Expression="o.texts">
<div>Ein Text: <?="text"?></div>
</Iterate>
<h1>Boolean Conditionals Test</h1>
<If Expression="o.True"><h1>YES</h1></If>
<If Expression="!o.True"><h1>!YES</h1></If>
<If Expression="o.False"><h1>NO</h1></If>
<If Expression="!o.False"><h1>!NO</h1></If>
<table>
<thead>
<tr>
<td>#</td>
<td>Spalte 1</td>
<td>Spalte 2</td>
<td>Spalte 3</td>
<td>Spalte 4</td>
</tr>
</thead>
<Iterate Key="n" Expression="/StaticTest/Loop">
<tr>
<td>#<Var Expression="n"/></td>
<td><Var>/StaticTest/Random</Var></td>
<td><Var>/StaticTest/Random</Var></td>
<td><Var>/StaticTest/Random</Var></td>
<td><Var>/StaticTest/Random</Var></td>
</tr>
</Iterate>
</table>
</SharpForm>

View File

@ -0,0 +1,54 @@
<html>
<head>
<title>appsrv test page</title>
<script type="text/javascript" src="scripts.js"></script>
<script type="text/javascript">
function lookup(o) {
var pattern = o.value;
if (o.lastpattern != pattern)
{
o.lastpattern = pattern;
var url = o.getAttribute("lookup") + "?pattern=" + pattern;
callDATALIST(url, o);
}
return false;
}
</script>
<style>
.popup {
display: block;
position: relative;
min-width: 240px;
max-width: 100%;
min-height: 80px;
max-height: 240px;
background-color: red;
}
</style>
</head>
<body>
<h1>appsrv test page</h1>
<p>A paragraph to see the file is displayed.</p>
<form action="/StaticTest/Debug">
Debug Message:
<input type="text" name="debugMessage" value="" />
<input type="submit" value="Senden" />
</form>
<form action="/StaticTest/Add">
Add two Numbers:
<input type="text" name="a" value="1" />
<input type="text" name="b" value="1" />
<input type="submit" value="Calculate" />
</form>
<form action="">
JSON Lookup Test:
<input type="text" name="pattern" list="pattern-ac-list" onkeyup="lookup(this);" lookup="/StaticTest/LookupList" autocomplete="off"/>
<datalist id="pattern-ac-list"></datalist>
</form>
</body>
</html>

View File

@ -0,0 +1,62 @@
function $(id){
return document.getElementById(id);
}
function pathSplit(path){
var s = path.split("/");
if (s[0] == ""){
s.splice(0,1);
}
return s;
}
function pathJoin(path){
return path.join("/");
}
function callURL(url, content, asyncReceiver = null, method = "GET"){
var r = new XMLHttpRequest();
var async = asyncReceiver != null;
r.open(method,url,async);
if (async) {
r.onload = asyncReceiver;
}
r.send(content);
if (!async){
return r.response;
}
}
function callJSON(url, parameters){
var reply = callURL(url, JSON.stringify( parameters ), null, "POST" );
return JSON.parse( reply );
}
function loadHTML(container,source){
container.innerHTML = source;
var scripts = container.getElementsByTagName("script");
for (var n=0;n<scripts.length;n++){
eval.call(window,scripts[n].innerText);
}
}
function callDATALIST(url,o,parameters)
{
var reply = callJSON(url, parameters);
var datalist = $(o.name + "-ac-list");
datalist.innerHTML = "";
for (var n=0;n<reply.length;n++)
{
var opt = document.createElement("OPTION");
opt.value = reply[n];
datalist.appendChild(opt);
}
return datalist;
}

View File

@ -0,0 +1,62 @@
function $(id){
return document.getElementById(id);
}
function pathSplit(path){
var s = path.split("/");
if (s[0] == ""){
s.splice(0,1);
}
return s;
}
function pathJoin(path){
return path.join("/");
}
function callURL(url, content, asyncReceiver = null, method = "GET"){
var r = new XMLHttpRequest();
var async = asyncReceiver != null;
r.open(method,url,async);
if (async) {
r.onload = asyncReceiver;
}
r.send(content);
if (!async){
return r.response;
}
}
function callJSON(url, parameters){
var reply = callURL(url, JSON.stringify( parameters ), null, "POST" );
return JSON.parse( reply );
}
function loadHTML(container,source){
container.innerHTML = source;
var scripts = container.getElementsByTagName("script");
for (var n=0;n<scripts.length;n++){
eval.call(window,scripts[n].innerText);
}
}
function callDATALIST(url,o,parameters)
{
var reply = callJSON(url, parameters);
var datalist = $(o.name + "-ac-list");
datalist.innerHTML = "";
for (var n=0;n<reply.length;n++)
{
var opt = document.createElement("OPTION");
opt.value = reply[n];
datalist.appendChild(opt);
}
return datalist;
}

View File

@ -0,0 +1 @@


View File

@ -1,9 +0,0 @@
<html>
<head>
<title>appsrv test page</title>
</head>
<body>
<h1>appsrv test page</h1>
<p>A paragraph to see the file is displayed.</p>
</body>
</html>