Initial Commit

master
U-WALDRENNACH\haraldwolff 2020-11-17 23:46:07 +01:00
commit 259c1275a5
63 changed files with 5604 additions and 0 deletions

41
.gitignore vendored 100644
View File

@ -0,0 +1,41 @@
# Autosave files
*~
# build
[Oo]bj/
[Bb]in/
packages/
TestResults/
# globs
Makefile.in
*.DS_Store
*.sln.cache
*.suo
*.cache
*.pidb
*.userprefs
*.usertasks
config.log
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.user
*.tar.gz
tarballs/
test-results/
Thumbs.db
.vs/
# Mac bundle stuff
*.dmg
*.app
# resharper
*_Resharper.*
*.Resharper
# dotCover
*.dotCover

View File

@ -0,0 +1,38 @@
// /**
// * File: AuthenticationProvider.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Collections.Generic;
namespace ln.http
{
public abstract class AuthenticationProvider
{
public AuthenticationProvider()
{
}
public abstract IEnumerable<HttpUser> EnumerateUsers();
public abstract HttpUser Authenticate(HttpRequest httpRequest);
public virtual HttpUser GetHttpUser(String authenticationName)
{
foreach (HttpUser httpUser in EnumerateUsers())
{
if (httpUser.AuthenticationName.Equals(authenticationName))
{
return httpUser;
}
}
throw new KeyNotFoundException();
}
}
}

View File

@ -0,0 +1,22 @@
// /**
// * File: AuthorizationMask.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.http
{
public static class AuthorizationMask
{
public static readonly long A_ACCESS = (1l << 0);
public static readonly long A_READ = (1l << 0);
public static readonly long A_WRITE = (1l << 0);
public static readonly long A_EXEC = (1l << 0);
public static readonly long A_SUPER = (-1);
}
}

245
HTTPServer.cs 100644
View File

@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using ln.logging;
using ln.threading;
using ln.application;
using ln.http.listener;
using ln.http.connections;
using System.Globalization;
using ln.http.exceptions;
using System.Threading;
using ln.type;
using ln.http.router;
namespace ln.http
{
public class HTTPServer
{
public static int backlog = 5;
public static int defaultPort = 8080;
public static bool exclusivePortListener = false;
public IHttpRouter Router { get; set; }
public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0);
public Logger Logger { get; set; }
bool shutdown = false;
List<Listener> listeners = new List<Listener>();
public Listener[] Listeners => listeners.ToArray();
DynamicPool threadPool;
public DynamicPool ThreadPool => threadPool;
HashSet<Connection> currentConnections = new HashSet<Connection>();
public IEnumerable<Connection> CurrentConnections => currentConnections;
public HTTPServer()
{
Logger = Logger.Default;
threadPool = new DynamicPool(1024);
}
public HTTPServer(IHttpRouter router)
: this()
{
Router = router;
}
public HTTPServer(Listener listener, IHttpRouter router)
: this(router)
{
AddListener(listener);
}
public HTTPServer(Endpoint endpoint, IHttpRouter router)
: this(new HttpListener(endpoint), router) { }
public void AddListener(Listener listener)
{
listeners.Add(listener);
if (IsRunning)
StartListener(listener);
}
public void StartListener(Listener listener)
{
listener.Open();
threadPool.Enqueue(
() => listener.AcceptMany(
(connection) => threadPool.Enqueue(
() => this.HandleConnection(connection)
)
)
);
}
public void StopListener(Listener listener)
{
listener.Close();
}
public void AddEndpoint(Endpoint endpoint)
{
AddListener(new HttpListener(endpoint));
}
public void Start()
{
threadPool.Start();
foreach (Listener listener in listeners)
StartListener(listener);
}
public void Stop()
{
lock (this)
{
this.shutdown = true;
}
foreach (Listener listener in listeners)
StopListener(listener);
for (int n = 0; n < 150; n++)
{
lock (currentConnections)
{
if (currentConnections.Count == 0)
break;
if ((n % 20) == 0)
{
Logging.Log(LogLevel.INFO, "HTTPServer: still waiting for {0} connections to close", currentConnections.Count);
}
}
Thread.Sleep(100);
}
lock (currentConnections)
{
foreach (Connection connection in currentConnections)
{
connection.Close();
}
}
threadPool.Stop(true);
}
private void HandleConnection(Connection connection)
{
lock (this.currentConnections)
currentConnections.Add(connection);
try
{
HttpRequest httpRequest = null;
bool keepAlive = true;
try
{
do
{
httpRequest = connection.ReadRequest(this);
if (httpRequest == null)
break;
HttpResponse response;
try
{
response = Router.Route(new HttpRoutingContext(httpRequest),httpRequest);
}
catch (HttpException httpExc)
{
response = new HttpResponse(httpRequest);
response.StatusCode = httpExc.StatusCode;
response.StatusMessage = httpExc.Message;
response.ContentWriter.WriteLine(httpExc.Message);
}
if (response != null)
{
keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive");
response.SetHeader("connection", keepAlive ? "keep-alive" : "close");
connection.SendResponse(response);
}
else
{
keepAlive = false;
}
response?.ContentStream?.Dispose();
} while (keepAlive);
}
catch (Exception e)
{
Logging.Log(e);
if (httpRequest != null)
{
HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 500;
httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
httpResponse.ContentWriter.Flush();
connection.SendResponse(httpResponse);
}
}
HttpRequest.ClearCurrent();
connection.GetStream().Close();
} finally
{
lock (currentConnections)
currentConnections.Remove(connection);
}
}
public void Log(DateTime startTime,double duration,HttpRequest httpRequest,HttpResponse httpResponse)
{
Logger.Log(LogLevel.INFO, "{0} {1} {2} {3}",startTime.ToString("yyyyMMdd-HH:mm:ss"),duration.ToString(CultureInfo.InvariantCulture),httpRequest.Hostname,httpRequest.RequestURL);
}
public static void StartSimpleServer(string[] arguments)
{
ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[]
{
new Argument('p',"port",8080),
new Argument('l',"listen","127.0.0.1"),
new Argument('c', "catch",null)
});
argumentContainer.Parse(ref arguments);
SimpleRouter router = new SimpleRouter();
router.AddSimpleRoute("/*", new RouterTarget((request) =>
{
HttpResponse response = new HttpResponse(request);
response.StatusCode = 404;
response.SetHeader("content-type", "text/plain");
response.ContentWriter.WriteLine("404 Not Found");
response.ContentWriter.Flush();
return response;
}), -100);
foreach (String path in arguments)
{
StaticRouter staticRouter = new StaticRouter(path);
staticRouter.AddIndex("index.html");
staticRouter.AddIndex("index.htm");
router.AddSimpleRoute("/*", staticRouter);
}
if (argumentContainer['c'].Value != null)
router.AddSimpleRoute("/*", new RouterTarget((request) => router.Route(new HttpRoutingContext(request, argumentContainer['c'].Value), request)), 0);
HTTPServer server = new HTTPServer(new Endpoint(IPv6.Parse(argumentContainer['l'].Value),int.Parse(argumentContainer['p'].Value)),
new LoggingRouter(router));
server.Start();
}
}
}

View File

@ -0,0 +1,157 @@
using System;
using System.Net.Sockets;
using ln.threading;
using System.Net;
using ln.type;
using ln.logging;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
namespace ln.http
{
//public delegate void HTTPServerConnectionEvent(HTTPServerConnection connection);
//public class HTTPServerConnection : PoolJob
//{
// public static ThreadLocal<HTTPServerConnection> Current { get; } = new ThreadLocal<HTTPServerConnection>();
// static HashSet<HTTPServerConnection> currentConnections = new HashSet<HTTPServerConnection>();
// public static HTTPServerConnection[] CurrentConnections => currentConnections.ToArray();
// public HTTPServer HTTPServer { get; }
// public TcpClient TcpClient { get; }
// public HttpRequest CurrentRequest { get; protected set; }
// public event HTTPServerConnectionEvent AbortRequested;
// public DateTime Created { get; }
// public DateTime Interpreted { get; set; }
// public DateTime Finished { get; set; }
// public HTTPServerConnection(HTTPServer httpServer,TcpClient tcpClient)
// {
// HTTPServer = httpServer;
// TcpClient = tcpClient;
// Created = DateTime.Now;
// }
// public virtual HttpResponse GetResponse(HttpRequest httpRequest,HttpApplication httpApplication) => httpApplication.GetResponse(httpRequest);
// public virtual void Abort()
// {
// if (AbortRequested != null)
// AbortRequested(this);
// }
// public override void RunJob()
// {
// HTTPServerConnection saveCurrent = Current.Value;
// Current.Value = this;
// lock (currentConnections)
// currentConnections.Add(this);
// try
// {
// setState("reading http request");
// HttpReader httpReader = new HttpReader(TcpClient.GetStream());
// httpReader.Read();
// if (!httpReader.Valid)
// return;
// HttpResponse response = null;
// using (CurrentRequest = new HttpRequest(this.HTTPServer,httpReader, (IPEndPoint)TcpClient.Client.LocalEndPoint))
// {
// Interpreted = DateTime.Now;
// try
// {
// HttpApplication application = HTTPServer.GetHttpApplication(new URI(CurrentRequest.BaseURI.ToString()));
// application.Authenticate(CurrentRequest);
// application.Authorize(CurrentRequest);
// setState("handling http request");
// response = GetResponse(CurrentRequest, application);
// }
// catch (Exception e)
// {
// setState("handling exception");
// response = new HttpResponse(CurrentRequest, "text/plain");
// response.StatusCode = 500;
// response.ContentWriter.WriteLine("Exception caught: {0}", e);
// }
// setState("sending response");
// if (response == null)
// {
// Logging.Log(LogLevel.DEBUG, "Request {0} returned no Response", CurrentRequest);
// }
// else
// {
// if (!response.HasCustomContentStream)
// {
// response.ContentWriter.Flush();
// MemoryStream cstream = (MemoryStream)response.ContentStream;
// cstream.Position = 0;
// response.SetHeader("content-length", cstream.Length.ToString());
// }
// if (CurrentRequest.Session != null)
// HTTPServer?.SessionCache?.ApplySessionID(response, CurrentRequest.Session);
// response.AddCookie("LN_SEEN", DateTime.Now.ToString());
// SendResponse(TcpClient.GetStream(), response);
// TcpClient.Close();
// Finished = DateTime.Now;
// HTTPServer.Log(Created, (Finished - Created).TotalMilliseconds, CurrentRequest, response);
// }
// }
// }
// finally
// {
// Current.Value = saveCurrent;
// lock (currentConnections)
// currentConnections.Remove(this);
// }
// }
// public static void SendResponse(Stream stream, HttpResponse response)
// {
// StreamWriter streamWriter = new StreamWriter(stream);
// streamWriter.NewLine = "\r\n";
// streamWriter.WriteLine("{0} {1} {2}", response.HttpRequest.Protocol, response.StatusCode, response.StatusMessage);
// foreach (String headerName in response.GetHeaderNames())
// {
// streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName));
// }
// foreach (HttpCookie httpCookie in response.Cookies)
// {
// streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString());
// }
// streamWriter.WriteLine();
// streamWriter.Flush();
// response.ContentStream.CopyTo(stream);
// response.ContentStream.Close();
// response.ContentStream.Dispose();
// streamWriter.Flush();
// }
//}
}

41
HttpCookie.cs 100644
View File

@ -0,0 +1,41 @@
using System;
using System.IO;
using System.Text;
namespace ln.http
{
public class HttpCookie
{
public String Name { get; set; }
public String Value { get; set; }
public bool Secure { get; set; }
public DateTime Expires { get; set; }
public String Path { get; set; }
public String Domain { get; set; }
public HttpCookie(String name)
{
Name = name;
}
public HttpCookie(String name,String value)
{
Name = name;
Value = value;
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendFormat("{0}={1}", Name, Value);
if (Secure)
stringBuilder.Append(";SECURE");
if (Path != null)
stringBuilder.AppendFormat(";Path={0}", Path);
return stringBuilder.ToString();
}
}
}

31
HttpHeader.cs 100644
View File

@ -0,0 +1,31 @@
using System;
namespace ln.http
{
public class HttpHeader
{
public String Name { get; }
public String Value { get; }
public HttpHeader(String rawHeader)
{
int colon = rawHeader.IndexOf(':');
if (colon < 0)
throw new FormatException("rawHeader must contain at least one colon");
Name = rawHeader.Substring(0, colon).Trim().ToUpper();
Value = rawHeader.Substring(colon + 1).Trim();
}
public HttpHeader(String headerName,String headerValue)
{
if (String.Empty.Equals(headerName))
throw new ArgumentException("headerName needs to contain at least one character", nameof(headerName));
Name = headerName.ToUpper();
Value = headerValue.ToUpper();
}
}
}

36
HttpHeaders.cs 100644
View File

@ -0,0 +1,36 @@
using System;
using ln.collections;
using System.Collections.Generic;
using System.Collections;
namespace ln.http
{
public class HttpHeaders : IEnumerable<HttpHeader>
{
MappingBTree<string, HttpHeader> headers = new MappingBTree<string, HttpHeader>((value) => value.Name);
public HttpHeaders()
{
}
public HttpHeader this[string headerName] => headers[headerName.ToUpper()];
public bool Contains(string headerName) => headers.ContainsKey(headerName.ToUpper());
public void Add(String headerName, String headerValue) => Add(new HttpHeader(headerName, headerValue));
public void Add(HttpHeader httpHeader) => headers.Add(httpHeader);
public void Remove(HttpHeader httpHeader) => headers.Remove(httpHeader);
public void Remove(string headerName) => headers.RemoveKey(headerName.ToUpper());
public void Set(String headerName, String headerValue)
{
if (headers.ContainsKey(headerName))
Remove(headerName);
Add(new HttpHeader(headerName, headerValue));
}
public IEnumerator<HttpHeader> GetEnumerator() => headers.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => headers.Values.GetEnumerator();
}
}

320
HttpReader.cs 100644
View File

@ -0,0 +1,320 @@
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Net;
using ln.type;
using ln.http.message;
using ln.http.io;
using ln.http.message.parser;
namespace ln.http
{
//public class HttpReader
//{
// delegate bool ReadCondition(int b);
// public Stream Stream { get; }
// private byte[] buffer = new byte[8192];
// private int hlen;
// private int blen;
// private int bptr;
// public Endpoint RemoteEndpoint { get; private set; }
// public HeaderContainer Headers { get; private set; }
// public String Method { get; private set; }
// public String URL { get; private set; }
// public String Protocol { get; private set; }
// //public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();
// public bool Valid { get; private set; } = false;
// public HttpReader(Stream stream)
// {
// Stream = stream;
// }
// public HttpReader(Stream stream,Endpoint remoteEndpoint)
// {
// Stream = stream;
// RemoteEndpoint = remoteEndpoint;
// }
// public void Read()
// {
// UnbufferedStreamReader reader = new UnbufferedStreamReader(Stream);
// string requestLine = reader.ReadLine();
// string[] requestTokens = requestLine.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
// if (requestTokens.Length != 3)
// throw new FormatException("request line malformed");
// Method = requestTokens[0];
// URL = requestTokens[1];
// Protocol = requestTokens[2];
// Headers = HTTP.ReadHeader(reader);
// Valid = true;
// }
// public int ReadByte()
// {
// if (bptr >= blen)
// return -1;
// return buffer[bptr++];
// }
// public int Current
// {
// get
// {
// if (bptr >= blen)
// return -1;
// return buffer[bptr];
// }
// }
// public void Reverse()
// {
// if ((bptr > 0) && (bptr < blen))
// bptr--;
// }
// public void ReadRequestHead()
// {
// bptr = 0;
// do
// {
// int rlen = Stream.Read(buffer, blen, buffer.Length - blen);
// if (rlen == 0)
// throw new IOException();
// blen += rlen;
// while (bptr <= (blen - 4))
// {
// if (
// (buffer[bptr + 0] == '\r') &&
// (buffer[bptr + 1] == '\n') &&
// (buffer[bptr + 2] == '\r') &&
// (buffer[bptr + 3] == '\n')
// )
// {
// hlen = bptr;
// bptr = 0;
// return;
// }
// bptr++;
// }
// byte[] nbuffer = new byte[buffer.Length << 1];
// Array.Copy(buffer, nbuffer, buffer.Length);
// buffer = nbuffer;
// } while (blen >= buffer.Length);
// bptr = 0;
// }
// private String ReadConditional(ReadCondition readCondition,bool reverse = true)
// {
// StringBuilder stringBuilder = new StringBuilder();
// int b = ReadByte();
// while ((b != -1) && (readCondition(b)) )
// {
// stringBuilder.Append((char)b);
// b = ReadByte();
// }
// if (reverse)
// Reverse();
// return stringBuilder.ToString();
// }
// public void SkipWhiteSpace()
// {
// while (Char.IsWhiteSpace((char)ReadByte())) { };
// Reverse();
// }
// public String ReadToken()
// {
// return ReadConditional((b) => !Char.IsWhiteSpace((char)b));
// }
// public String ReadLine()
// {
// int p = bptr;
// while (p < blen - 1)
// {
// if ((buffer[p] == '\r') && (buffer[p + 1] == '\n'))
// {
// break;
// }
// p++;
// }
// string result = Encoding.ASCII.GetString(buffer, bptr, p - bptr);
// bptr = p + 2;
// return result;
// }
// public String ReadHeaderName()
// {
// return ReadConditional((b) => b != ':', false).ToUpper();
// }
// public String ReadHeaderValue()
// {
// String value = ReadLine();
// while (Char.IsWhiteSpace((char)Current))
// {
// value = value + ReadLine();
// }
// return value.Trim();
// }
// //public void ReadHeaders()
// //{
// // while (bptr < hlen)
// // {
// // String name = ReadHeaderName();
// // String value = ReadHeaderValue();
// // Headers.Add(name, value);
// // }
// //}
// public int ReadRequestBody(byte[] dst,int offset,int length)
// {
// //int nRead = 0;
// //if (bptr < blen)
// //{
// // int len = Math.Min(length, blen - bptr);
// // Array.Copy(buffer, bptr, dst, offset, len);
// // bptr += len;
// // length -= len;
// // offset += len;
// // nRead += len;
// //}
// int nRead = 0;
// while (length > 0)
// {
// int nr = Stream.Read(dst, offset, length);
// if (nr > 0)
// {
// nRead += nr;
// length -= nr;
// offset += nr;
// }
// }
// return nRead;
// }
// /*
// byte[] buffer;
// int blen = 0;
// int bptr = 0;
// public HttpReader(Stream stream)
// {
// Stream = stream;
// buffer = new byte[1024];
// }
// public HttpReader(Stream stream, int buffersize)
// {
// Stream = stream;
// buffer = new byte[buffersize];
// }
// private int read()
// {
// if (bptr >= blen)
// {
// bptr = 0;
// blen = Stream.Read(buffer, 0, buffer.Length);
// if (blen <= 0)
// throw new EndOfStreamException();
// }
// return buffer[bptr++];
// }
// private int ReadTo(byte[] b,int offset,byte[] mark)
// {
// int pm = 0;
// int p = offset;
// while (p < b.Length)
// {
// b[p] = (byte)read();
// if (b[p] == mark[pm])
// {
// pm++;
// if (pm >= mark.Length)
// {
// p++;
// break;
// }
// }
// else
// {
// pm = 0;
// }
// p++;
// }
// return p;
// }
// public String ReadRequestLine(int maxSize = 1024)
// {
// byte[] b = new byte[maxSize];
// int l = ReadTo(b, 0, new byte[] { 0x0d, 0x0a });
// return Encoding.ASCII.GetString(b, 0, l);
// }
// public Dictionary<string,string> ReadHTTPHeaders()
// {
// byte[] b = new byte[8192];
// int hlen = ReadTo(b, 0, new byte[] { 0x0d, 0x0a, 0x0d, 0x0a });
// Dictionary<string, string> headers = new Dictionary<string, string>();
// string rawHeaders = Encoding.ASCII.GetString(b, 0, hlen);
// String[] rawLines = rawHeaders.Split(new String[] { "\r\n" }, StringSplitOptions.None);
// for (int n = rawLines.Length-1; n >= 0 ; n--)
// {
// if ((rawLines[n].Length > 0) && (Char.IsWhiteSpace(rawLines[n][0])))
// {
// rawLines[n - 1] = rawLines[n - 1] + rawLines[n];
// rawLines[n] = null;
// }
// }
// foreach (String rawLine in rawLines)
// {
// if (rawLine != null)
// {
// int colon = rawLine.IndexOf(':');
// if (colon > 0)
// {
// String name = rawLine.Substring(0, colon).Trim().ToUpper();
// String value = rawLine.Substring(colon + 1).Trim();
// headers.Add(name, value);
// }
// }
// }
// return headers;
// }
// public byte[] ReadRequestBody(byte[] buffer)
// {
// return null;
// }
// */
//}
}

272
HttpRequest.cs 100644
View File

@ -0,0 +1,272 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using ln.type;
using ln.http.exceptions;
using System.Threading;
using ln.http.session;
using ln.http.message;
using ln.http.io;
using ln.http.message.parser;
namespace ln.http
{
public class HttpRequest : IDisposable
{
static ThreadLocal<HttpRequest> current = new ThreadLocal<HttpRequest>();
static public HttpRequest Current => current.Value;
//Dictionary<String, String> requestHeaders;
HeaderContainer requestHeaders;
Dictionary<String, String> requestCookies;
Dictionary<string, String> requestParameters;
public HTTPServer HTTPServer { get; }
public Endpoint RemoteEndpoint { get; private set; }
public Endpoint LocalEndpoint { get; private set; }
public Uri BaseURI { get; 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 Session Session { get; set; }
public HttpUser CurrentUser => Session.CurrentUser;
public HeaderContainer RequestHeaders => requestHeaders;
MemoryStream contentStream;
public MemoryStream ContentStream
{
get
{
if (contentStream == null)
ReadRequestBody();
return contentStream;
}
}
public TextReader ContentReader
{
get
{
if (contentReader == null)
contentReader = new StreamReader(ContentStream);
return contentReader;
}
}
int requestBodyLength;
byte[] requestBody;
StreamReader contentReader;
Stream connectionStream;
UnbufferedStreamReader connectionReader;
public Stream GetConnectionStream() => connectionStream;
public HttpRequest(HTTPServer httpServer, Stream clientStream, Endpoint localEndpoint, Endpoint remoteEndpoint)
{
HTTPServer = httpServer;
connectionStream = clientStream;
connectionReader = new UnbufferedStreamReader(connectionStream);
LocalEndpoint = localEndpoint;
RemoteEndpoint = remoteEndpoint;
ReadRequestLine();
requestHeaders = HTTP.ReadHeader(connectionReader);
requestCookies = new Dictionary<string, string>();
requestParameters = new Dictionary<string, string>();
Setup();
requestBodyLength = int.Parse(GetRequestHeader("content-length", "0"));
}
void ReadRequestLine()
{
string requestLine = connectionReader.ReadLine();
string[] requestTokens = requestLine.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
if (requestTokens.Length != 3)
throw new BadRequestException();
Method = requestTokens[0];
RequestURL = requestTokens[1];
Protocol = requestTokens[2];
}
public void ReadRequestBody()
{
requestBody = new byte[requestBodyLength];
if (requestBodyLength > 0)
{
int nRead = 0;
int length = requestBodyLength;
while (length > 0)
{
int nr = connectionStream.Read(requestBody, nRead, length);
if (nr > 0)
{
nRead += nr;
length -= nr;
}
}
}
contentStream = new MemoryStream(requestBody);
}
public void FinishRequest()
{
if ((requestBodyLength > 0) && (requestBody == null))
{
int nRead = 0;
int length = requestBodyLength;
byte[] discard = new byte[8192];
while (length > 0)
{
int nr = connectionStream.Read(discard,0,length > discard.Length ? discard.Length : length);
if (nr > 0)
{
nRead += nr;
length -= nr;
}
}
}
}
public void MakeCurrent() => current.Value = this;
public static void ClearCurrent() => current.Value = null;
private void Setup()
{
SetupResourceURI();
SetupCookies();
}
/*
* SetupResourceURI()
*
* Setup the following fields:
*
* - Hostname
* - Port
* - BaseURI
* - URI
* - Query
*
*/
private void SetupResourceURI()
{
String host = GetRequestHeader("HOST");
String[] hostTokens = host.Split(':');
Hostname = hostTokens[0];
Port = (hostTokens.Length > 1) ? int.Parse(hostTokens[1]) : LocalEndpoint.Port;
BaseURI = new UriBuilder("http", Hostname, Port).Uri;
URI = new Uri(BaseURI, RequestURL);
Query = new QueryStringParameters(URI.Query);
}
private void SetupCookies()
{
string cookies = GetRequestHeader("COOKIE");
foreach (String cookie in cookies.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
string[] c = cookie.Split(new char[] { '=' }, 2);
string cn, cv;
cn = c[0].Trim();
if (c.Length > 1)
cv = c[1].Trim();
else
cv = "";
if (!this.requestCookies.ContainsKey(cn))
this.requestCookies.Add(cn, cv);
}
}
public override string ToString()
{
//return string.Format("[HttpRequest: RemoteEndpoint={0}, Hostname={1} Port={2} URI={4}, Method={4}, RequestURL={5}, Protocol={6} Query={7}]", RemoteEndpoint, URI, Method, RequestURL, Protocol, Hostname, Port,Query);
return base.ToString();
}
public String GetRequestHeader(String name)
{
return GetRequestHeader(name, "");
}
public String GetRequestHeader(String name, String def)
{
name = name.ToUpper();
if (requestHeaders.ContainsKey(name))
return requestHeaders[name].Value;
return def;
}
public String[] RequestHeaderNames => requestHeaders.Keys.ToArray();
public String[] CookieNames => requestCookies.Keys.ToArray();
public bool ContainsCookie(String name)
{
return this.requestCookies.ContainsKey(name);
}
public String GetCookie(String name)
{
return requestCookies[name];
}
public bool ContainsParameter(string parameterName) => requestParameters.ContainsKey(parameterName);
public String GetParameter(String parameterName) => GetParameter(parameterName, null);
public String GetParameter(String parameterName,String defaultValue)
{
if (!requestParameters.TryGetValue(parameterName, out string value))
value = defaultValue;
return value;
}
public void SetParameter(String parameterName,String parameterValue) => requestParameters[parameterName] = parameterValue;
public IEnumerable<string> ParameterNames => requestParameters.Keys;
public string self()
{
return BaseURI.ToString();
}
public HttpResponse Redirect(string location, params object[] p) => Redirect(303, location, p);
public HttpResponse Redirect(int status, string location, params object[] p)
{
location = string.Format(location, p);
HttpResponse response = new HttpResponse(this);
response.StatusCode = status;
response.SetHeader("location", location);
return response;
}
public void Dispose()
{
contentReader?.Dispose();
ContentStream?.Dispose();
}
}
}

135
HttpResponse.cs 100644
View File

@ -0,0 +1,135 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
namespace ln.http
{
public class HttpResponse
{
public HttpRequest HttpRequest { get; }
public Stream ContentStream { get; private set; }
public TextWriter ContentWriter { get; private set; }
public bool HasCustomContentStream { get; private set; }
int statusCode;
string statusMessage;
Dictionary<string, List<String>> headers = new Dictionary<string, List<string>>();
List<HttpCookie> cookies = new List<HttpCookie>();
public HttpResponse(HttpRequest httpRequest)
{
HttpRequest = httpRequest;
ContentStream = new MemoryStream();
ContentWriter = new StreamWriter(ContentStream);
StatusCode = 200;
SetHeader("content-type", "text/html");
}
public HttpResponse(HttpRequest httpRequest,string contentType)
:this(httpRequest)
{
SetHeader("content-type", contentType);
}
public HttpResponse(HttpRequest httpRequest, Stream contentStream)
{
HttpRequest = httpRequest;
ContentStream = contentStream;
ContentWriter = null;
HasCustomContentStream = true;
StatusCode = 200;
SetHeader("content-type", "text/html");
}
public HttpResponse(HttpRequest httpRequest, Stream contentStream,string contentType)
:this(httpRequest,contentStream)
{
SetHeader("content-type", contentType);
}
public String GetHeader(string name) => GetHeader(name, null);
public String GetHeader(string name, string defValue)
{
return headers.ContainsKey(name) ? String.Join(",", headers[name.ToUpper()]) : defValue;
}
public String[] GetHeaderValues(string name)
{
return headers[name.ToUpper()].ToArray();
}
public String[] GetHeaderNames()
{
return headers.Keys.ToArray();
}
public void SetHeader(String name, String value)
{
name = name.ToUpper();
headers[name] = new List<string>();
headers[name].Add(value);
}
public void AddHeader(String name, String value)
{
name = name.ToUpper();
if (!headers.ContainsKey(name))
headers[name] = new List<string>();
headers[name].Add(value);
}
public void RemoveHeader(String name)
{
headers.Remove(name.ToUpper());
}
public bool ContainsHeader(string headerName)
{
return headers.ContainsKey(headerName.ToUpper());
}
public void AddCookie(string name,string value)
{
AddCookie(new HttpCookie(name, value));
}
public void AddCookie(HttpCookie httpCookie)
{
cookies.Add(httpCookie);
}
public void RemoveCookie(HttpCookie httpCookie)
{
cookies.Remove(httpCookie);
}
public HttpCookie[] Cookies => cookies.ToArray();
public int StatusCode
{
get
{
return statusCode;
}
set {
statusCode = value;
statusMessage = HttpStatusCodes.GetStatusMessage(statusCode);
}
}
public String StatusMessage
{
get {
return statusMessage;
}
set {
statusMessage = value;
}
}
}
}

36
HttpRouter.cs 100644
View File

@ -0,0 +1,36 @@
using System;
using ln.logging;
using ln.http.router;
namespace ln.http
{
//public abstract class HttpRouter : IHttpRouter
//{
// public HttpRouter()
// {
// }
// public abstract IHTTPResource FindResource(HttpRequest httpRequest);
// public virtual HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
// {
// try
// {
// IHTTPResource resource = FindResource(httpRequest);
// return resource.GetResponse(httpRequest);
// } catch (Exception e)
// {
// Logging.Log(e);
// if (httpRequest != null)
// {
// HttpResponse httpResponse = new HttpResponse(httpRequest);
// httpResponse.StatusCode = 500;
// httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
// return httpResponse;
// }
// return null;
// }
// }
//}
}

25
HttpStatusCodes.cs 100644
View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace ln.http
{
public class HttpStatusCodes
{
static Dictionary<int, string> statusMessages = new Dictionary<int, string>()
{
{ 200, "Ok" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 204, "No Content" },
{ 403, "Access denied" },
{ 404, "Not Found" },
{ 500, "Internal Error" }
};
public static String GetStatusMessage(int code){
if (statusMessages.ContainsKey(code))
return statusMessages[code];
return "";
}
}
}

30
HttpUser.cs 100644
View File

@ -0,0 +1,30 @@
// /**
// * File: HttpUser.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.http
{
public class HttpUser
{
public String AuthenticationName { get; private set; }
public virtual String DisplayName { get; private set; }
public long AccessRightsMask { get; private set; }
public HttpUser()
{
AuthenticationName = "";
DisplayName = "Anonymous";
AccessRightsMask = 0;
}
}
}

8
IHTTPResource.cs 100644
View File

@ -0,0 +1,8 @@
using System;
namespace ln.http
{
public interface IHTTPResource
{
HttpResponse GetResponse(HttpRequest httpRequest);
}
}

9
IHttpRouter.cs 100644
View File

@ -0,0 +1,9 @@
using System;
using ln.http.router;
namespace ln.http
{
public interface IHttpRouter
{
HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest);
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ln.http
{
public class QueryStringParameters : IDictionary<String,String>
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
public QueryStringParameters(String query)
{
if (query.StartsWith("?"))
query = query.Substring(1);
String[] pairs = query.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
foreach (String pair in pairs)
{
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;
}
}
}
public string this[string key] {
get => parameters[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 string GetValue(string key,string defaultValue)
{
if (!ContainsKey(key))
return defaultValue;
return parameters[key];
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("[Query");
foreach (String key in parameters.Keys){
stringBuilder.AppendFormat(" {0}={1}", key, parameters[key]);
}
stringBuilder.Append("]");
return stringBuilder.ToString();
}
}
}

View File

@ -0,0 +1,59 @@
// /**
// * File: CertContainer.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
using System.IO;
namespace ln.http.cert
{
public class CertContainer
{
public string SearchPath { get; set; }
Dictionary<string, X509Certificate> certificates = new Dictionary<string, X509Certificate>();
public CertContainer(){ }
public CertContainer(string searchPath)
{
SearchPath = searchPath;
}
public void AddCertificate(string targetHost, X509Certificate certificate) => certificates[targetHost] = certificate;
public virtual X509Certificate LookupCertificate(string targetHost)
{
String p = Path.Combine(SearchPath, String.Format("{0}.pem",targetHost));
if (File.Exists(p))
{
return X509Certificate.CreateFromCertFile(p);
}
return null;
}
public X509Certificate SelectCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
if (!certificates.ContainsKey(targetHost) && (SearchPath != null))
{
X509Certificate certificate = LookupCertificate(targetHost);
if (certificate != null)
{
certificates[targetHost] = certificate;
}
else
{
return null;
}
}
return certificates[targetHost];
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ln.http.client
{
public class CookieContainer : IEnumerable<HttpCookie>
{
public HttpCookie[] Cookies => cookies.ToArray();
List<HttpCookie> cookies = new List<HttpCookie>();
public CookieContainer()
{
}
public void Add(HttpCookie httpCookie)
{
foreach (HttpCookie cookie in Get(httpCookie.Domain))
{
if (cookie.Name.Equals(httpCookie.Name))
Remove(cookie);
}
cookies.Add(httpCookie);
}
public void Remove(HttpCookie httpCookie)
{
cookies.Remove(httpCookie);
}
public IEnumerable<HttpCookie> Get(string domain)
{
return Cookies.Where((c) => c.Domain.Equals(domain));
}
public IEnumerator<HttpCookie> GetEnumerator() => cookies.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => cookies.GetEnumerator();
}
}

View File

@ -0,0 +1,18 @@
using System;
using ln.type;
namespace ln.http.client
{
public class HttpClient
{
CookieContainer Cookies { get; set; } = new CookieContainer();
public HttpClient()
{
}
public HttpClientRequest CreateRequest(URI uri)
{
return new HttpClientRequest(this, uri);
}
}
}

View File

@ -0,0 +1,107 @@
using System;
using ln.type;
using System.IO;
using System.Net.Sockets;
using System.Net.Security;
namespace ln.http.client
{
public class HttpClientRequest
{
public HttpClient HttpClient { get; }
public URI URI
{
get => uri;
set
{
if (!value.Scheme.Equals("http") && !value.Scheme.Equals("https"))
throw new ArgumentOutOfRangeException(nameof(value), String.Format("unsupported url scheme: {0}", value.Scheme));
uri = value;
}
}
public HttpHeaders Headers { get; } = new HttpHeaders();
public String Method { get; set; }
URI uri;
MemoryStream contentStream;
HttpClientResponse clientResponse;
public HttpClientRequest(HttpClient httpClient, URI uri)
{
HttpClient = httpClient;
URI = uri;
Headers.Add("user-agent", "ln.http.client");
}
public Stream GetContentStream()
{
if (clientResponse != null)
throw new NotSupportedException("Request has already been executed, content stream has been disposed");
if (contentStream == null)
contentStream = new MemoryStream();
return contentStream;
}
public HttpClientResponse GetResponse()
{
if (clientResponse == null)
{
executeRequest();
}
return clientResponse;
}
private Stream OpenConnection()
{
TcpClient tcpClient = null;
SslStream sslStream = null;
try
{
tcpClient = new TcpClient();
tcpClient.ExclusiveAddressUse = false;
tcpClient.Connect(URI.Host, int.Parse(URI.Port));
if (!tcpClient.Connected)
throw new IOException(String.Format("could not connect to host {0}",uri.Host));
if (uri.Scheme.Equals("https"))
{
sslStream = new SslStream(tcpClient.GetStream());
return sslStream;
}
return tcpClient.GetStream();
} catch (Exception)
{
if (sslStream != null)
sslStream.Dispose();
if (tcpClient != null)
tcpClient.Dispose();
throw;
}
}
private void executeRequest()
{
if (contentStream.Length > 0)
{
byte[] requestContent = contentStream.ToArray();
Headers.Add("content-length", requestContent.Length.ToString());
}
Stream connectionStream = OpenConnection();
clientResponse = new HttpClientResponse(this);
contentStream.Dispose();
}
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace ln.http.client
{
public class HttpClientResponse
{
public HttpClientRequest ClientRequest { get; }
public HttpClientResponse(HttpClientRequest clientRequest)
{
ClientRequest = clientRequest;
}
}
}

View File

@ -0,0 +1,86 @@
// /**
// * File: Connection.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using ln.type;
using System.IO;
using ln.logging;
using ln.http.listener;
namespace ln.http.connections
{
public abstract class Connection : IDisposable
{
public Listener Listener { get; private set; }
public abstract IPv6 RemoteHost { get; }
public abstract int RemotePort { get; }
public abstract Stream GetStream();
public Connection(Listener listener)
{
Listener = listener;
}
public virtual HttpRequest ReadRequest(HTTPServer httpServer)
{
try
{
return new HttpRequest(httpServer, GetStream(), Listener.LocalEndpoint, new Endpoint(RemoteHost, RemotePort));
} catch (IOException)
{
return null;
} catch (Exception e)
{
Logging.Log(e);
return null;
}
}
public abstract void Close();
public virtual void Dispose()
{
Close();
Listener = null;
}
public virtual void SendResponse(HttpResponse response) => SendResponse(GetStream(), response);
public static void SendResponse(Stream stream, HttpResponse response)
{
response.HttpRequest.FinishRequest();
response.SetHeader("Content-Length", response.ContentStream.Length.ToString());
StreamWriter streamWriter = new StreamWriter(stream);
streamWriter.NewLine = "\r\n";
streamWriter.WriteLine("{0} {1} {2}", response.HttpRequest.Protocol, response.StatusCode, response.StatusMessage);
foreach (String headerName in response.GetHeaderNames())
{
streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName));
}
foreach (HttpCookie httpCookie in response.Cookies)
{
streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString());
}
streamWriter.WriteLine();
streamWriter.Flush();
response.ContentStream.Position = 0;
response.ContentStream.CopyTo(stream);
response.ContentStream.Close();
response.ContentStream.Dispose();
stream.Flush();
}
}
}

View File

@ -0,0 +1,41 @@
// /**
// * File: HttpConnection.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using ln.type;
using System.Net.Sockets;
using ln.http.listener;
namespace ln.http.connections
{
public class HttpConnection : Connection
{
public TcpClient TcpClient { get; }
public Endpoint RemoteEndpoint { get; }
public HttpConnection(Listener listener, TcpClient tcpClient)
:base(listener)
{
TcpClient = tcpClient;
RemoteEndpoint = new Endpoint(TcpClient.Client.RemoteEndPoint);
}
public override IPv6 RemoteHost => RemoteEndpoint.Address;
public override int RemotePort => RemoteEndpoint.Port;
public override Stream GetStream() => TcpClient.GetStream();
public override void Close()
{
TcpClient.Close();
}
}
}

View File

@ -0,0 +1,46 @@
// /**
// * File: HttpsConnection.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using System.Security.AccessControl;
using ln.type;
using System.Net.Security;
using ln.http.listener;
namespace ln.http.connections
{
public class HttpsConnection : Connection
{
Connection Connection { get; }
SslStream sslStream { get; }
public override IPv6 RemoteHost => Connection.RemoteHost;
public override int RemotePort => Connection.RemotePort;
public HttpsConnection(Listener listener,Connection connection,LocalCertificateSelectionCallback localCertificateSelectionCallback)
:base(listener)
{
Connection = connection;
sslStream = new SslStream(connection.GetStream(),false, null, localCertificateSelectionCallback);
}
public override HttpRequest ReadRequest(HTTPServer server)
{
throw new NotImplementedException();
}
public override Stream GetStream() => sslStream;
public override void Close()
{
sslStream.Close();
}
}
}

View File

@ -0,0 +1,20 @@
// /**
// * File: BadRequest.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.http.exceptions
{
public class BadRequestException: HttpException
{
public BadRequestException()
:base(400,"Bad Request")
{
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace ln.http.exceptions
{
public class DisposeConnectionException : Exception
{
public DisposeConnectionException()
{
}
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace ln.http.exceptions
{
public class HttpException : Exception
{
public int StatusCode { get; } = 500;
public HttpException(String message)
: base(message)
{
}
public HttpException(String message, Exception innerException)
: base(message, innerException)
{
}
public HttpException(int statusCode,String message)
: base(message)
{
StatusCode = statusCode;
}
public HttpException(int statusCode,String message, Exception innerException)
: base(message, innerException)
{
StatusCode = statusCode;
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace ln.http.exceptions
{
public class IllegalRequestException : Exception
{
public IllegalRequestException(String requestLine)
:base(requestLine)
{
}
}
}

View File

@ -0,0 +1,21 @@
// /**
// * File: MethodNotSupportedException.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Net;
namespace ln.http.exceptions
{
public class MethodNotAllowedException : HttpException
{
public MethodNotAllowedException()
:base(405,"Method not allowed")
{
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace ln.http.exceptions
{
public class ResourceNotFoundException : HttpException
{
public ResourceNotFoundException(String resourcePath, String nextResource)
: base(404, String.Format("Could not find resource \"{0}\" within \"{1}\"", nextResource, resourcePath))
{
}
}
}

View File

@ -0,0 +1,20 @@
// /**
// * File: UnsupportedMediaTypeException.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
namespace ln.http.exceptions
{
public class UnsupportedMediaTypeException : HttpException
{
public UnsupportedMediaTypeException()
: base(415, "Unsupported Media Type")
{
}
}
}

View File

@ -0,0 +1,68 @@
// /**
// * File: UnbufferedStreamreader.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using System.Text;
namespace ln.http.io
{
public class UnbufferedStreamReader : TextReader
{
public Stream Stream { get; }
public UnbufferedStreamReader(Stream stream)
{
Stream = stream;
}
public override int Read() => Stream.ReadByte();
public override string ReadLine()
{
StringBuilder stringBuilder = new StringBuilder();
char ch;
while ((ch = (char)Stream.ReadByte()) != -1)
{
if (ch == '\r')
{
ch = (char)Stream.ReadByte();
if (ch == '\n')
return stringBuilder.ToString();
stringBuilder.Append('\r');
}
stringBuilder.Append(ch);
}
if ((ch == -1) && (stringBuilder.Length == 0))
return null;
return stringBuilder.ToString();
}
public string ReadToken()
{
StringBuilder stringBuilder = new StringBuilder();
char ch = (char)Stream.ReadByte();
while (char.IsWhiteSpace(ch))
ch = (char)Stream.ReadByte();
while (!char.IsWhiteSpace(ch))
{
stringBuilder.Append(ch);
ch = (char)Stream.ReadByte();
}
return stringBuilder.ToString();
}
}
}

View File

@ -0,0 +1,53 @@
// /**
// * File: HttpListener.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System.Net.Sockets;
using ln.type;
using ln.http.connections;
namespace ln.http.listener
{
public class HttpListener : Listener
{
protected TcpListener tcpListener;
public HttpListener(int port) :this(IPv6.ANY,port){}
public HttpListener(Endpoint endpoint) : this(endpoint.Address, endpoint.Port) {}
public HttpListener(IPv6 listen, int port)
: base(listen, port)
{
}
public override Connection Accept() => new HttpConnection(this,tcpListener.AcceptTcpClient());
public override bool IsOpen => (tcpListener != null);
public override void Open()
{
tcpListener = new TcpListener(Listen, Port);
tcpListener.Start();
}
public override void Close()
{
if (tcpListener != null)
{
tcpListener.Stop();
tcpListener = null;
}
}
public override void Dispose()
{
if (IsOpen)
Close();
}
}
}

View File

@ -0,0 +1,27 @@
// /**
// * File: HttpsListener.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using ln.type;
using ln.http.connections;
using ln.http.cert;
namespace ln.http.listener
{
public class HttpsListener : HttpListener
{
public HttpsListener(int port) : this(IPv6.ANY, port) { }
public HttpsListener(IPv6 listen, int port) : base(listen, port) { }
public CertContainer CertContainer { get; set; } = new CertContainer();
public override Connection Accept()
{
return new HttpsConnection(this, base.Accept(),CertContainer.SelectCertificate);
}
}
}

View File

@ -0,0 +1,43 @@
// /**
// * File: Listener.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using ln.http.connections;
using ln.type;
namespace ln.http.listener
{
public abstract class Listener : IDisposable
{
public IPv6 Listen { get; }
public int Port { get; }
public Endpoint LocalEndpoint => new Endpoint(Listen, Port);
public abstract bool IsOpen { get; }
protected Listener(IPv6 listen, int port)
{
Listen = listen;
Port = port;
}
public virtual void AcceptMany(Action<Connection> handler)
{
while (IsOpen)
handler(Accept());
}
public abstract void Open();
public abstract void Close();
public abstract Connection Accept();
public abstract void Dispose();
}
}

24
ln.http.csproj 100644
View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.1.0</Version>
<Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company>
<Description />
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>http server</PackageTags>
<AssemblyVersion>0.0.1.0</AssemblyVersion>
<FileVersion>0.0.1.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ln.application\ln.application.csproj" />
<ProjectReference Include="..\ln.collections\ln.collections.csproj" />
<ProjectReference Include="..\ln.logging\ln.logging.csproj" />
<ProjectReference Include="..\ln.threading\ln.threading.csproj" />
<ProjectReference Include="..\ln.type\ln.type.csproj" />
</ItemGroup>
</Project>

159
message/Header.cs 100644
View File

@ -0,0 +1,159 @@
// /**
// * File: Header.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Net.Sockets;
namespace ln.http.message
{
public class Header
{
public string Name { get; }
string rawvalue;
public string RawValue {
get => rawvalue;
set => SetValue(value);
}
string comments;
public string Comments => comments;
string value;
public string Value
{
get => value;
set => SetValue(value);
}
Dictionary<string, string> parameters;
public Header(string headerLine)
{
int colon = headerLine.IndexOf(':');
if (colon == -1)
throw new FormatException("expected to find :");
Name = headerLine.Substring(0, colon).ToUpper();
SetValue(headerLine.Substring(colon + 1));
}
public Header(string name, string value)
{
Name = name.ToUpper();
SetValue(value);
}
public void SetValue(string newValue)
{
rawvalue = newValue;
value = ParseValue(new StringReader(newValue.Trim()),out comments);
// at least MIME Content-* header follow the parameter syntax...
if (Name.StartsWith("CONTENT-", StringComparison.InvariantCulture))
{
ParseParameters();
}
}
public bool ContainsParameter(string parameterName) => parameters.ContainsKey(parameterName.ToUpper());
public string GetParameter(string parameterName) => parameters[parameterName.ToUpper()];
public string GetParameter(string parameterName,string defaultValue) => parameters[parameterName.ToUpper()];
string ParseComment(TextReader reader)
{
StringBuilder commentBuilder = new StringBuilder();
ParseComment(reader, commentBuilder);
return commentBuilder.ToString();
}
void ParseComment(TextReader reader,StringBuilder commentBuilder)
{
int ch;
while (((ch = reader.Read()) != -1) && (ch != ')'))
commentBuilder.Append((char)ch);
}
public virtual string ParseValue(TextReader reader,out string parsedComments)
{
StringBuilder stringBuilder = new StringBuilder();
StringBuilder commentBuilder = new StringBuilder();
int ch;
while (((ch = reader.Read())!=-1))
{
if (ch == '(')
{
commentBuilder.Append(ParseComment(reader));
} else
{
stringBuilder.Append((char)ch);
}
}
parsedComments = commentBuilder.ToString().Trim();
return stringBuilder.ToString().Trim();
}
public void ParseParameters()
{
if (parameters != null)
return;
parameters = new Dictionary<string, string>();
int semicolon = value.IndexOf(';');
if (semicolon > 0)
{
TokenReader tokenReader = new TokenReader(new StringReader(value.Substring(semicolon)));
while (tokenReader.Peek() != -1)
{
if (tokenReader.Read() != ';')
throw new FormatException();
string pName = tokenReader.ReadToken().ToUpper();
if (tokenReader.Read() != '=')
throw new FormatException("expected =");
string pValue = (tokenReader.Peek() == '"') ? tokenReader.ReadQuotedString() : tokenReader.ReadToken();
parameters.Add(pName, pValue);
}
value = value.Substring(0, semicolon).Trim();
}
}
//void parseValue(string v)
//{
// rawValue = v;
// TokenReader tokenReader = new TokenReader(parseComments(new StringReader(v)));
// StringBuilder stringBuilder = new StringBuilder();
// int ch;
// while (((ch = tokenReader.Read()) != -1) && (ch != ';'))
// stringBuilder.Append((char)ch);
// Value = stringBuilder.ToString();
// while (tokenReader.Peek() != -1)
// {
// string pName = tokenReader.ReadToken();
// if (tokenReader.Read() != '=')
// throw new FormatException("expected =");
// string pValue = (tokenReader.Peek() == '"') ? tokenReader.ReadQuotedString() : tokenReader.ReadToken();
// parameters.Add(pName, pValue);
// }
//}
public override int GetHashCode() => Name.GetHashCode();
public override bool Equals(object obj) => (obj is Header you) && Name.Equals(you.Name);
}
}

View File

@ -0,0 +1,76 @@
// /**
// * File: HeaderContainer.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ln.http.io;
namespace ln.http.message
{
public class HeaderContainer
{
Dictionary<string, Header> headers = new Dictionary<string, Header>();
public HeaderContainer()
{
}
public HeaderContainer(Stream stream):this(new UnbufferedStreamReader(stream))
{
}
public HeaderContainer(TextReader reader)
{
List<String> headerLines = new List<string>();
string currentline = reader.ReadLine();
while (!currentline.Equals(string.Empty))
{
if (char.IsWhiteSpace(currentline[0]))
{
headerLines[headerLines.Count - 1] = headerLines[headerLines.Count - 1] + currentline;
}
else
{
headerLines.Add(currentline);
}
currentline = reader.ReadLine();
}
foreach (string headerLine in headerLines)
{
Header header = new Header(headerLine);
headers.Add(header.Name, header);
}
}
public Header this[string name]
{
get => headers[name.ToUpper()];
}
public void Add(Header header)=> headers.Add(header.Name, header);
public bool ContainsKey(string name) => headers.ContainsKey(name.ToUpper());
public bool Contains(string name) => headers.ContainsKey(name.ToUpper());
public string Get(string name) => this[name].Value;
public void Set(string name,string value)
{
name = name.ToUpper();
if (!headers.TryGetValue(name,out Header header))
{
header = new Header(name);
headers.Add(name, header);
}
header.Value = value;
}
public void Remove(string name) => headers.Remove(name.ToUpper());
public IEnumerable<string> Keys => headers.Keys;
}
}

153
message/Message.cs 100644
View File

@ -0,0 +1,153 @@
// /**
// * File: Message.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Globalization;
using ln.http.io;
using ln.type;
using ln.http.message.parser;
namespace ln.http.message
{
public class Message
{
public HeaderContainer Headers { get; private set; }
byte[] bodyData;
int bodyOffset;
int bodyLength;
List<Message> parts;
bool isMultipart;
public bool IsMultipart => isMultipart;
public Message()
{
Setup(new HeaderContainer(), new byte[0], 0, 0);
}
public Message(HeaderContainer headers, byte[] body)
: this(headers, body, 0, body.Length) { }
public Message(HeaderContainer headers, byte[] body, int offset, int length)
{
Setup(headers, body, offset, length);
}
public Message(byte[] body, int offset, int length)
{
MemoryStream memoryStream = new MemoryStream(body, offset, length);
HeaderContainer headers = MIME.ReadHeader(new UnbufferedStreamReader(memoryStream));
if (memoryStream.Position >= length)
throw new FormatException("MIME header section too long");
Setup(headers, body, offset + (int)memoryStream.Position, length - (int)memoryStream.Position);
}
public Message(Stream stream)
{
HeaderContainer headers = MIME.ReadHeader(new UnbufferedStreamReader(stream));
byte[] data = stream.ReadToEnd();
Setup(headers, data, 0, data.Length);
}
private void Setup(HeaderContainer headers, byte[] body, int offset, int length)
{
Headers = headers;
bodyData = body;
bodyOffset = offset;
bodyLength = length;
string ct = Headers["Content-Type"].Value;
isMultipart = ct.StartsWith("multipart/", StringComparison.InvariantCulture) || ct.StartsWith("message/", StringComparison.InvariantCulture);
}
public void ReadParts()
{
parts = new List<Message>();
if (isMultipart)
{
string boundary = Headers["Content-Type"].GetParameter("boundary");
string delimiter = "--" + boundary;
int[] indeces = FindIndeces(bodyData, bodyOffset, bodyLength, Encoding.ASCII.GetBytes(delimiter));
for (int n = 1; n < indeces.Length; n++)
{
Message part = new Message(bodyData, indeces[n - 1], indeces[n] - indeces[n - 1]);
parts.Add(part);
}
}
}
int[] FindIndeces(byte[] data,int offset,int length,byte[] pattern)
{
List<int> offsets = new List<int>();
List<int> validated = new List<int>();
for (int n = offset; n < (length - pattern.Length); n++)
{
int p = 0;
while ((p < pattern.Length) && (data[n + p] == pattern[p]))
p++;
if (p == pattern.Length)
{
if ((n == offset) || ((n >= (offset + 2)) && (data[offset + n - 2] == '\r') && (data[offset + n - 1] == '\n')))
{
n += pattern.Length;
while ((n < (offset + length)) && (data[n - 2] != '\r') && (data[n - 1] != '\n'))
n++;
validated.Add(n);
if (((offset + length) > (n + 1)) && (data[n] == '-') && (data[n + 1] == '-'))
break;
}
}
}
return validated.ToArray();
}
public Stream OpenBodyStream() => new MemoryStream(bodyData, bodyOffset, bodyLength);
public IEnumerable<Message> Parts
{
get
{
if (parts == null)
ReadParts();
return parts;
}
}
public bool HasHeader(string name) => Headers.Contains(name);
public Header GetHeader(string name) => Headers[name];
public void SetHeader(string name, string value) => Headers.Set(name, value);
public void RemoveHeader(String name) => Headers.Remove(name);
public override string ToString()
{
return base.ToString();
}
}
}

View File

@ -0,0 +1,767 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ln.http.message
{
public static class MimeTypeMap
{
private static readonly Lazy<IDictionary<string, string>> _mappings = new Lazy<IDictionary<string, string>>(BuildMappings);
private static IDictionary<string, string> BuildMappings()
{
var mappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
#region Big freaking list of mime types
// maps both ways,
// extension -> mime type
// and
// mime type -> extension
//
// any mime types on left side not pre-loaded on right side, are added automatically
// some mime types can map to multiple extensions, so to get a deterministic mapping,
// add those to the dictionary specifcially
//
// combination of values from Windows 7 Registry and
// from C:\Windows\System32\inetsrv\config\applicationHost.config
// some added, including .7z and .dat
//
// Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml
// which lists mime types, but not extensions
//
{".323", "text/h323"},
{".3g2", "video/3gpp2"},
{".3gp", "video/3gpp"},
{".3gp2", "video/3gpp2"},
{".3gpp", "video/3gpp"},
{".7z", "application/x-7z-compressed"},
{".aa", "audio/audible"},
{".AAC", "audio/aac"},
{".aaf", "application/octet-stream"},
{".aax", "audio/vnd.audible.aax"},
{".ac3", "audio/ac3"},
{".aca", "application/octet-stream"},
{".accda", "application/msaccess.addin"},
{".accdb", "application/msaccess"},
{".accdc", "application/msaccess.cab"},
{".accde", "application/msaccess"},
{".accdr", "application/msaccess.runtime"},
{".accdt", "application/msaccess"},
{".accdw", "application/msaccess.webapplication"},
{".accft", "application/msaccess.ftemplate"},
{".acx", "application/internet-property-stream"},
{".AddIn", "text/xml"},
{".ade", "application/msaccess"},
{".adobebridge", "application/x-bridge-url"},
{".adp", "application/msaccess"},
{".ADT", "audio/vnd.dlna.adts"},
{".ADTS", "audio/aac"},
{".afm", "application/octet-stream"},
{".ai", "application/postscript"},
{".aif", "audio/aiff"},
{".aifc", "audio/aiff"},
{".aiff", "audio/aiff"},
{".air", "application/vnd.adobe.air-application-installer-package+zip"},
{".amc", "application/mpeg"},
{".anx", "application/annodex"},
{".apk", "application/vnd.android.package-archive" },
{".application", "application/x-ms-application"},
{".art", "image/x-jg"},
{".asa", "application/xml"},
{".asax", "application/xml"},
{".ascx", "application/xml"},
{".asd", "application/octet-stream"},
{".asf", "video/x-ms-asf"},
{".ashx", "application/xml"},
{".asi", "application/octet-stream"},
{".asm", "text/plain"},
{".asmx", "application/xml"},
{".aspx", "application/xml"},
{".asr", "video/x-ms-asf"},
{".asx", "video/x-ms-asf"},
{".atom", "application/atom+xml"},
{".au", "audio/basic"},
{".avi", "video/x-msvideo"},
{".axa", "audio/annodex"},
{".axs", "application/olescript"},
{".axv", "video/annodex"},
{".bas", "text/plain"},
{".bcpio", "application/x-bcpio"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".cab", "application/octet-stream"},
{".caf", "audio/x-caf"},
{".calx", "application/vnd.ms-office.calx"},
{".cat", "application/vnd.ms-pki.seccat"},
{".cc", "text/plain"},
{".cd", "text/plain"},
{".cdda", "audio/aiff"},
{".cdf", "application/x-cdf"},
{".cer", "application/x-x509-ca-cert"},
{".cfg", "text/plain"},
{".chm", "application/octet-stream"},
{".class", "application/x-java-applet"},
{".clp", "application/x-msclip"},
{".cmd", "text/plain"},
{".cmx", "image/x-cmx"},
{".cnf", "text/plain"},
{".cod", "image/cis-cod"},
{".config", "application/xml"},
{".contact", "text/x-ms-contact"},
{".coverage", "application/xml"},
{".cpio", "application/x-cpio"},
{".cpp", "text/plain"},
{".crd", "application/x-mscardfile"},
{".crl", "application/pkix-crl"},
{".crt", "application/x-x509-ca-cert"},
{".cs", "text/plain"},
{".csdproj", "text/plain"},
{".csh", "application/x-csh"},
{".csproj", "text/plain"},
{".css", "text/css"},
{".csv", "text/csv"},
{".cur", "application/octet-stream"},
{".cxx", "text/plain"},
{".dat", "application/octet-stream"},
{".datasource", "application/xml"},
{".dbproj", "text/plain"},
{".dcr", "application/x-director"},
{".def", "text/plain"},
{".deploy", "application/octet-stream"},
{".der", "application/x-x509-ca-cert"},
{".dgml", "application/xml"},
{".dib", "image/bmp"},
{".dif", "video/x-dv"},
{".dir", "application/x-director"},
{".disco", "text/xml"},
{".divx", "video/divx"},
{".dll", "application/x-msdownload"},
{".dll.config", "text/xml"},
{".dlm", "text/dlm"},
{".doc", "application/msword"},
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".dot", "application/msword"},
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
{".dsp", "application/octet-stream"},
{".dsw", "text/plain"},
{".dtd", "text/xml"},
{".dtsConfig", "text/xml"},
{".dv", "video/x-dv"},
{".dvi", "application/x-dvi"},
{".dwf", "drawing/x-dwf"},
{".dwg", "application/acad"},
{".dwp", "application/octet-stream"},
{".dxf", "application/x-dxf" },
{".dxr", "application/x-director"},
{".eml", "message/rfc822"},
{".emz", "application/octet-stream"},
{".eot", "application/vnd.ms-fontobject"},
{".eps", "application/postscript"},
{".es", "application/ecmascript"},
{".etl", "application/etl"},
{".etx", "text/x-setext"},
{".evy", "application/envoy"},
{".exe", "application/vnd.microsoft.portable-executable"},
{".exe.config", "text/xml"},
{".f4v", "video/mp4"},
{".fdf", "application/vnd.fdf"},
{".fif", "application/fractals"},
{".filters", "application/xml"},
{".fla", "application/octet-stream"},
{".flac", "audio/flac"},
{".flr", "x-world/x-vrml"},
{".flv", "video/x-flv"},
{".fsscript", "application/fsharp-script"},
{".fsx", "application/fsharp-script"},
{".generictest", "application/xml"},
{".gif", "image/gif"},
{".gpx", "application/gpx+xml"},
{".group", "text/x-ms-group"},
{".gsm", "audio/x-gsm"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".hdf", "application/x-hdf"},
{".hdml", "text/x-hdml"},
{".hhc", "application/x-oleobject"},
{".hhk", "application/octet-stream"},
{".hhp", "application/octet-stream"},
{".hlp", "application/winhlp"},
{".hpp", "text/plain"},
{".hqx", "application/mac-binhex40"},
{".hta", "application/hta"},
{".htc", "text/x-component"},
{".htm", "text/html"},
{".html", "text/html"},
{".htt", "text/webviewhtml"},
{".hxa", "application/xml"},
{".hxc", "application/xml"},
{".hxd", "application/octet-stream"},
{".hxe", "application/xml"},
{".hxf", "application/xml"},
{".hxh", "application/octet-stream"},
{".hxi", "application/octet-stream"},
{".hxk", "application/xml"},
{".hxq", "application/octet-stream"},
{".hxr", "application/octet-stream"},
{".hxs", "application/octet-stream"},
{".hxt", "text/html"},
{".hxv", "application/xml"},
{".hxw", "application/octet-stream"},
{".hxx", "text/plain"},
{".i", "text/plain"},
{".ico", "image/x-icon"},
{".ics", "application/octet-stream"},
{".idl", "text/plain"},
{".ief", "image/ief"},
{".iii", "application/x-iphone"},
{".inc", "text/plain"},
{".inf", "application/octet-stream"},
{".ini", "text/plain"},
{".inl", "text/plain"},
{".ins", "application/x-internet-signup"},
{".ipa", "application/x-itunes-ipa"},
{".ipg", "application/x-itunes-ipg"},
{".ipproj", "text/plain"},
{".ipsw", "application/x-itunes-ipsw"},
{".iqy", "text/x-ms-iqy"},
{".isp", "application/x-internet-signup"},
{".isma", "application/octet-stream"},
{".ismv", "application/octet-stream"},
{".ite", "application/x-itunes-ite"},
{".itlp", "application/x-itunes-itlp"},
{".itms", "application/x-itunes-itms"},
{".itpc", "application/x-itunes-itpc"},
{".IVF", "video/x-ivf"},
{".jar", "application/java-archive"},
{".java", "application/octet-stream"},
{".jck", "application/liquidmotion"},
{".jcz", "application/liquidmotion"},
{".jfif", "image/pjpeg"},
{".jnlp", "application/x-java-jnlp-file"},
{".jpb", "application/octet-stream"},
{".jpe", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/javascript"},
{".json", "application/json"},
{".jsx", "text/jscript"},
{".jsxbin", "text/plain"},
{".latex", "application/x-latex"},
{".library-ms", "application/windows-library+xml"},
{".lit", "application/x-ms-reader"},
{".loadtest", "application/xml"},
{".lpk", "application/octet-stream"},
{".lsf", "video/x-la-asf"},
{".lst", "text/plain"},
{".lsx", "video/x-la-asf"},
{".lzh", "application/octet-stream"},
{".m13", "application/x-msmediaview"},
{".m14", "application/x-msmediaview"},
{".m1v", "video/mpeg"},
{".m2t", "video/vnd.dlna.mpeg-tts"},
{".m2ts", "video/vnd.dlna.mpeg-tts"},
{".m2v", "video/mpeg"},
{".m3u", "audio/x-mpegurl"},
{".m3u8", "audio/x-mpegurl"},
{".m4a", "audio/m4a"},
{".m4b", "audio/m4b"},
{".m4p", "audio/m4p"},
{".m4r", "audio/x-m4r"},
{".m4v", "video/x-m4v"},
{".mac", "image/x-macpaint"},
{".mak", "text/plain"},
{".man", "application/x-troff-man"},
{".manifest", "application/x-ms-manifest"},
{".map", "text/plain"},
{".master", "application/xml"},
{".mbox", "application/mbox"},
{".mda", "application/msaccess"},
{".mdb", "application/x-msaccess"},
{".mde", "application/msaccess"},
{".mdp", "application/octet-stream"},
{".me", "application/x-troff-me"},
{".mfp", "application/x-shockwave-flash"},
{".mht", "message/rfc822"},
{".mhtml", "message/rfc822"},
{".mid", "audio/mid"},
{".midi", "audio/mid"},
{".mix", "application/octet-stream"},
{".mk", "text/plain"},
{".mk3d", "video/x-matroska-3d"},
{".mka", "audio/x-matroska"},
{".mkv", "video/x-matroska"},
{".mmf", "application/x-smaf"},
{".mno", "text/xml"},
{".mny", "application/x-msmoney"},
{".mod", "video/mpeg"},
{".mov", "video/quicktime"},
{".movie", "video/x-sgi-movie"},
{".mp2", "video/mpeg"},
{".mp2v", "video/mpeg"},
{".mp3", "audio/mpeg"},
{".mp4", "video/mp4"},
{".mp4v", "video/mp4"},
{".mpa", "video/mpeg"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpf", "application/vnd.ms-mediapackage"},
{".mpg", "video/mpeg"},
{".mpp", "application/vnd.ms-project"},
{".mpv2", "video/mpeg"},
{".mqv", "video/quicktime"},
{".ms", "application/x-troff-ms"},
{".msg", "application/vnd.ms-outlook"},
{".msi", "application/octet-stream"},
{".mso", "application/octet-stream"},
{".mts", "video/vnd.dlna.mpeg-tts"},
{".mtx", "application/xml"},
{".mvb", "application/x-msmediaview"},
{".mvc", "application/x-miva-compiled"},
{".mxf", "application/mxf"},
{".mxp", "application/x-mmxp"},
{".nc", "application/x-netcdf"},
{".nsc", "video/x-ms-asf"},
{".nws", "message/rfc822"},
{".ocx", "application/octet-stream"},
{".oda", "application/oda"},
{".odb", "application/vnd.oasis.opendocument.database"},
{".odc", "application/vnd.oasis.opendocument.chart"},
{".odf", "application/vnd.oasis.opendocument.formula"},
{".odg", "application/vnd.oasis.opendocument.graphics"},
{".odh", "text/plain"},
{".odi", "application/vnd.oasis.opendocument.image"},
{".odl", "text/plain"},
{".odm", "application/vnd.oasis.opendocument.text-master"},
{".odp", "application/vnd.oasis.opendocument.presentation"},
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
{".odt", "application/vnd.oasis.opendocument.text"},
{".oga", "audio/ogg"},
{".ogg", "audio/ogg"},
{".ogv", "video/ogg"},
{".ogx", "application/ogg"},
{".one", "application/onenote"},
{".onea", "application/onenote"},
{".onepkg", "application/onenote"},
{".onetmp", "application/onenote"},
{".onetoc", "application/onenote"},
{".onetoc2", "application/onenote"},
{".opus", "audio/ogg"},
{".orderedtest", "application/xml"},
{".osdx", "application/opensearchdescription+xml"},
{".otf", "application/font-sfnt"},
{".otg", "application/vnd.oasis.opendocument.graphics-template"},
{".oth", "application/vnd.oasis.opendocument.text-web"},
{".otp", "application/vnd.oasis.opendocument.presentation-template"},
{".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
{".ott", "application/vnd.oasis.opendocument.text-template"},
{".oxt", "application/vnd.openofficeorg.extension"},
{".p10", "application/pkcs10"},
{".p12", "application/x-pkcs12"},
{".p7b", "application/x-pkcs7-certificates"},
{".p7c", "application/pkcs7-mime"},
{".p7m", "application/pkcs7-mime"},
{".p7r", "application/x-pkcs7-certreqresp"},
{".p7s", "application/pkcs7-signature"},
{".pbm", "image/x-portable-bitmap"},
{".pcast", "application/x-podcast"},
{".pct", "image/pict"},
{".pcx", "application/octet-stream"},
{".pcz", "application/octet-stream"},
{".pdf", "application/pdf"},
{".pfb", "application/octet-stream"},
{".pfm", "application/octet-stream"},
{".pfx", "application/x-pkcs12"},
{".pgm", "image/x-portable-graymap"},
{".pic", "image/pict"},
{".pict", "image/pict"},
{".pkgdef", "text/plain"},
{".pkgundef", "text/plain"},
{".pko", "application/vnd.ms-pki.pko"},
{".pls", "audio/scpls"},
{".pma", "application/x-perfmon"},
{".pmc", "application/x-perfmon"},
{".pml", "application/x-perfmon"},
{".pmr", "application/x-perfmon"},
{".pmw", "application/x-perfmon"},
{".png", "image/png"},
{".pnm", "image/x-portable-anymap"},
{".pnt", "image/x-macpaint"},
{".pntg", "image/x-macpaint"},
{".pnz", "image/png"},
{".pot", "application/vnd.ms-powerpoint"},
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
{".ppa", "application/vnd.ms-powerpoint"},
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
{".ppm", "image/x-portable-pixmap"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prf", "application/pics-rules"},
{".prm", "application/octet-stream"},
{".prx", "application/octet-stream"},
{".ps", "application/postscript"},
{".psc1", "application/PowerShell"},
{".psd", "application/octet-stream"},
{".psess", "application/xml"},
{".psm", "application/octet-stream"},
{".psp", "application/octet-stream"},
{".pst", "application/vnd.ms-outlook"},
{".pub", "application/x-mspublisher"},
{".pwz", "application/vnd.ms-powerpoint"},
{".qht", "text/x-html-insertion"},
{".qhtm", "text/x-html-insertion"},
{".qt", "video/quicktime"},
{".qti", "image/x-quicktime"},
{".qtif", "image/x-quicktime"},
{".qtl", "application/x-quicktimeplayer"},
{".qxd", "application/octet-stream"},
{".ra", "audio/x-pn-realaudio"},
{".ram", "audio/x-pn-realaudio"},
{".rar", "application/x-rar-compressed"},
{".ras", "image/x-cmu-raster"},
{".rat", "application/rat-file"},
{".rc", "text/plain"},
{".rc2", "text/plain"},
{".rct", "text/plain"},
{".rdlc", "application/xml"},
{".reg", "text/plain"},
{".resx", "application/xml"},
{".rf", "image/vnd.rn-realflash"},
{".rgb", "image/x-rgb"},
{".rgs", "text/plain"},
{".rm", "application/vnd.rn-realmedia"},
{".rmi", "audio/mid"},
{".rmp", "application/vnd.rn-rn_music_package"},
{".roff", "application/x-troff"},
{".rpm", "audio/x-pn-realaudio-plugin"},
{".rqy", "text/x-ms-rqy"},
{".rtf", "application/rtf"},
{".rtx", "text/richtext"},
{".rvt", "application/octet-stream" },
{".ruleset", "application/xml"},
{".s", "text/plain"},
{".safariextz", "application/x-safari-safariextz"},
{".scd", "application/x-msschedule"},
{".scr", "text/plain"},
{".sct", "text/scriptlet"},
{".sd2", "audio/x-sd2"},
{".sdp", "application/sdp"},
{".sea", "application/octet-stream"},
{".searchConnector-ms", "application/windows-search-connector+xml"},
{".setpay", "application/set-payment-initiation"},
{".setreg", "application/set-registration-initiation"},
{".settings", "application/xml"},
{".sgimb", "application/x-sgimb"},
{".sgml", "text/sgml"},
{".sh", "application/x-sh"},
{".shar", "application/x-shar"},
{".shtml", "text/html"},
{".sit", "application/x-stuffit"},
{".sitemap", "application/xml"},
{".skin", "application/xml"},
{".skp", "application/x-koan" },
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
{".slk", "application/vnd.ms-excel"},
{".sln", "text/plain"},
{".slupkg-ms", "application/x-ms-license"},
{".smd", "audio/x-smd"},
{".smi", "application/octet-stream"},
{".smx", "audio/x-smd"},
{".smz", "audio/x-smd"},
{".snd", "audio/basic"},
{".snippet", "application/xml"},
{".snp", "application/octet-stream"},
{".sol", "text/plain"},
{".sor", "text/plain"},
{".spc", "application/x-pkcs7-certificates"},
{".spl", "application/futuresplash"},
{".spx", "audio/ogg"},
{".src", "application/x-wais-source"},
{".srf", "text/plain"},
{".SSISDeploymentManifest", "text/xml"},
{".ssm", "application/streamingmedia"},
{".sst", "application/vnd.ms-pki.certstore"},
{".stl", "application/vnd.ms-pki.stl"},
{".sv4cpio", "application/x-sv4cpio"},
{".sv4crc", "application/x-sv4crc"},
{".svc", "application/xml"},
{".svg", "image/svg+xml"},
{".swf", "application/x-shockwave-flash"},
{".step", "application/step"},
{".stp", "application/step"},
{".t", "application/x-troff"},
{".tar", "application/x-tar"},
{".tcl", "application/x-tcl"},
{".testrunconfig", "application/xml"},
{".testsettings", "application/xml"},
{".tex", "application/x-tex"},
{".texi", "application/x-texinfo"},
{".texinfo", "application/x-texinfo"},
{".tgz", "application/x-compressed"},
{".thmx", "application/vnd.ms-officetheme"},
{".thn", "application/octet-stream"},
{".tif", "image/tiff"},
{".tiff", "image/tiff"},
{".tlh", "text/plain"},
{".tli", "text/plain"},
{".toc", "application/octet-stream"},
{".tr", "application/x-troff"},
{".trm", "application/x-msterminal"},
{".trx", "application/xml"},
{".ts", "video/vnd.dlna.mpeg-tts"},
{".tsv", "text/tab-separated-values"},
{".ttf", "application/font-sfnt"},
{".tts", "video/vnd.dlna.mpeg-tts"},
{".txt", "text/plain"},
{".u32", "application/octet-stream"},
{".uls", "text/iuls"},
{".user", "text/plain"},
{".ustar", "application/x-ustar"},
{".vb", "text/plain"},
{".vbdproj", "text/plain"},
{".vbk", "video/mpeg"},
{".vbproj", "text/plain"},
{".vbs", "text/vbscript"},
{".vcf", "text/x-vcard"},
{".vcproj", "application/xml"},
{".vcs", "text/plain"},
{".vcxproj", "application/xml"},
{".vddproj", "text/plain"},
{".vdp", "text/plain"},
{".vdproj", "text/plain"},
{".vdx", "application/vnd.ms-visio.viewer"},
{".vml", "text/xml"},
{".vscontent", "application/xml"},
{".vsct", "text/xml"},
{".vsd", "application/vnd.visio"},
{".vsi", "application/ms-vsi"},
{".vsix", "application/vsix"},
{".vsixlangpack", "text/xml"},
{".vsixmanifest", "text/xml"},
{".vsmdi", "application/xml"},
{".vspscc", "text/plain"},
{".vss", "application/vnd.visio"},
{".vsscc", "text/plain"},
{".vssettings", "text/xml"},
{".vssscc", "text/plain"},
{".vst", "application/vnd.visio"},
{".vstemplate", "text/xml"},
{".vsto", "application/x-ms-vsto"},
{".vsw", "application/vnd.visio"},
{".vsx", "application/vnd.visio"},
{".vtt", "text/vtt"},
{".vtx", "application/vnd.visio"},
{".wasm", "application/wasm"},
{".wav", "audio/wav"},
{".wave", "audio/wav"},
{".wax", "audio/x-ms-wax"},
{".wbk", "application/msword"},
{".wbmp", "image/vnd.wap.wbmp"},
{".wcm", "application/vnd.ms-works"},
{".wdb", "application/vnd.ms-works"},
{".wdp", "image/vnd.ms-photo"},
{".webarchive", "application/x-safari-webarchive"},
{".webm", "video/webm"},
{".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */
{".webtest", "application/xml"},
{".wiq", "application/xml"},
{".wiz", "application/msword"},
{".wks", "application/vnd.ms-works"},
{".WLMP", "application/wlmoviemaker"},
{".wlpginstall", "application/x-wlpg-detect"},
{".wlpginstall3", "application/x-wlpg3-detect"},
{".wm", "video/x-ms-wm"},
{".wma", "audio/x-ms-wma"},
{".wmd", "application/x-ms-wmd"},
{".wmf", "application/x-msmetafile"},
{".wml", "text/vnd.wap.wml"},
{".wmlc", "application/vnd.wap.wmlc"},
{".wmls", "text/vnd.wap.wmlscript"},
{".wmlsc", "application/vnd.wap.wmlscriptc"},
{".wmp", "video/x-ms-wmp"},
{".wmv", "video/x-ms-wmv"},
{".wmx", "video/x-ms-wmx"},
{".wmz", "application/x-ms-wmz"},
{".woff", "application/font-woff"},
{".woff2", "application/font-woff2"},
{".wpl", "application/vnd.ms-wpl"},
{".wps", "application/vnd.ms-works"},
{".wri", "application/x-mswrite"},
{".wrl", "x-world/x-vrml"},
{".wrz", "x-world/x-vrml"},
{".wsc", "text/scriptlet"},
{".wsdl", "text/xml"},
{".wvx", "video/x-ms-wvx"},
{".x", "application/directx"},
{".xaf", "x-world/x-vrml"},
{".xaml", "application/xaml+xml"},
{".xap", "application/x-silverlight-app"},
{".xbap", "application/x-ms-xbap"},
{".xbm", "image/x-xbitmap"},
{".xdr", "text/plain"},
{".xht", "application/xhtml+xml"},
{".xhtml", "application/xhtml+xml"},
{".xla", "application/vnd.ms-excel"},
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
{".xlc", "application/vnd.ms-excel"},
{".xld", "application/vnd.ms-excel"},
{".xlk", "application/vnd.ms-excel"},
{".xll", "application/vnd.ms-excel"},
{".xlm", "application/vnd.ms-excel"},
{".xls", "application/vnd.ms-excel"},
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".xlt", "application/vnd.ms-excel"},
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
{".xlw", "application/vnd.ms-excel"},
{".xml", "text/xml"},
{".xmp", "application/octet-stream" },
{".xmta", "application/xml"},
{".xof", "x-world/x-vrml"},
{".XOML", "text/plain"},
{".xpm", "image/x-xpixmap"},
{".xps", "application/vnd.ms-xpsdocument"},
{".xrm-ms", "text/xml"},
{".xsc", "application/xml"},
{".xsd", "text/xml"},
{".xsf", "text/xml"},
{".xsl", "text/xml"},
{".xslt", "text/xml"},
{".xsn", "application/octet-stream"},
{".xss", "application/xml"},
{".xspf", "application/xspf+xml"},
{".xtp", "application/octet-stream"},
{".xwd", "image/x-xwindowdump"},
{".z", "application/x-compress"},
{".zip", "application/zip"},
{"application/fsharp-script", ".fsx"},
{"application/msaccess", ".adp"},
{"application/msword", ".doc"},
{"application/octet-stream", ".bin"},
{"application/onenote", ".one"},
{"application/postscript", ".eps"},
{"application/step", ".step"},
{"application/vnd.ms-excel", ".xls"},
{"application/vnd.ms-powerpoint", ".ppt"},
{"application/vnd.ms-works", ".wks"},
{"application/vnd.visio", ".vsd"},
{"application/x-director", ".dir"},
{"application/x-shockwave-flash", ".swf"},
{"application/x-x509-ca-cert", ".cer"},
{"application/x-zip-compressed", ".zip"},
{"application/xhtml+xml", ".xhtml"},
{"application/xml", ".xml"}, // anomoly, .xml -> text/xml, but application/xml -> many thingss, but all are xml, so safest is .xml
{"audio/aac", ".AAC"},
{"audio/aiff", ".aiff"},
{"audio/basic", ".snd"},
{"audio/mid", ".midi"},
{"audio/wav", ".wav"},
{"audio/x-m4a", ".m4a"},
{"audio/x-mpegurl", ".m3u"},
{"audio/x-pn-realaudio", ".ra"},
{"audio/x-smd", ".smd"},
{"image/bmp", ".bmp"},
{"image/jpeg", ".jpg"},
{"image/pict", ".pic"},
{"image/png", ".png"}, //Defined in [RFC-2045], [RFC-2048]
{"image/x-png", ".png"}, //See https://www.w3.org/TR/PNG/#A-Media-type :"It is recommended that implementations also recognize the media type "image/x-png"."
{"image/tiff", ".tiff"},
{"image/x-macpaint", ".mac"},
{"image/x-quicktime", ".qti"},
{"message/rfc822", ".eml"},
{"text/html", ".html"},
{"text/plain", ".txt"},
{"text/scriptlet", ".wsc"},
{"text/xml", ".xml"},
{"video/3gpp", ".3gp"},
{"video/3gpp2", ".3gp2"},
{"video/mp4", ".mp4"},
{"video/mpeg", ".mpg"},
{"video/quicktime", ".mov"},
{"video/vnd.dlna.mpeg-tts", ".m2t"},
{"video/x-dv", ".dv"},
{"video/x-la-asf", ".lsf"},
{"video/x-ms-asf", ".asf"},
{"x-world/x-vrml", ".xof"},
#endregion
};
var cache = mappings.ToList(); // need ToList() to avoid modifying while still enumerating
foreach (var mapping in cache)
{
if (!mappings.ContainsKey(mapping.Value))
{
mappings.Add(mapping.Value, mapping.Key);
}
}
return mappings;
}
public static string GetMimeType(string extension)
{
if (extension == null)
{
throw new ArgumentNullException("extension");
}
if (!extension.StartsWith("."))
{
extension = "." + extension;
}
string mime;
return _mappings.Value.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
}
public static string GetExtension(string mimeType)
{
return GetExtension(mimeType, true);
}
public static string GetExtension(string mimeType, bool throwErrorIfNotFound)
{
if (mimeType == null)
{
throw new ArgumentNullException("mimeType");
}
if (mimeType.StartsWith("."))
{
throw new ArgumentException("Requested mime type is not valid: " + mimeType);
}
string extension;
if (_mappings.Value.TryGetValue(mimeType, out extension))
{
return extension;
}
if (throwErrorIfNotFound)
{
throw new ArgumentException("Requested mime type is not registered: " + mimeType);
}
else
{
return string.Empty;
}
}
}
}

View File

@ -0,0 +1,68 @@
// /**
// * File: TokenReader.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using System.Text;
using System.Linq;
namespace ln.http.message
{
public class TokenReader : TextReader
{
public static char[] specialChars = new char[] { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=' };
public TextReader BaseReader { get; }
public TokenReader(String text)
:this(new StringReader(text))
{}
public TokenReader(TextReader baseReader)
{
BaseReader = baseReader;
}
public override int Read() => BaseReader.Read();
public override int Peek() => BaseReader.Peek();
public string ReadToken()
{
while ((BaseReader.Peek() != -1) && char.IsWhiteSpace((char)BaseReader.Peek()))
BaseReader.Read();
StringBuilder stringBuilder = new StringBuilder();
int ch;
do
{
ch = BaseReader.Peek();
if ((ch <= ' ') || specialChars.Contains((char)ch))
return stringBuilder.ToString();
stringBuilder.Append((char)BaseReader.Read());
} while (ch != -1);
return stringBuilder.ToString();
}
public string ReadQuotedString()
{
StringBuilder stringBuilder = new StringBuilder();
if (BaseReader.Read() != '"')
throw new FormatException("quoted string must start with \"");
int ch;
while (((ch = BaseReader.Read()) != -1) && (ch != '"'))
stringBuilder.Append((char)ch);
return stringBuilder.ToString();
}
}
}

View File

@ -0,0 +1,40 @@
// /**
// * File: HTTP.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using System.Collections.Generic;
using ln.http.exceptions;
namespace ln.http.message.parser
{
public static class HTTP
{
public static HeaderContainer ReadHeader(TextReader reader)
{
List<String> headerLines = new List<string>();
string currentline = reader.ReadLine();
while (!currentline.Equals(string.Empty))
{
if (char.IsWhiteSpace(currentline[0]))
throw new BadRequestException();
headerLines.Add(currentline.Trim());
currentline = reader.ReadLine();
}
HeaderContainer headerContainer = new HeaderContainer();
foreach (string headerLine in headerLines)
headerContainer.Add(new Header(headerLine));
return headerContainer;
}
}
}

View File

@ -0,0 +1,43 @@
// /**
// * File: MIME.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using System.Collections.Generic;
namespace ln.http.message.parser
{
public static class MIME
{
public static HeaderContainer ReadHeader(TextReader reader)
{
List<String> headerLines = new List<string>();
string currentline = reader.ReadLine();
while (!currentline.Equals(string.Empty))
{
if (char.IsWhiteSpace(currentline[0]))
{
headerLines[headerLines.Count - 1] = headerLines[headerLines.Count - 1] + currentline;
}
else
{
headerLines.Add(currentline);
}
currentline = reader.ReadLine();
}
HeaderContainer headerContainer = new HeaderContainer();
foreach (string headerLine in headerLines)
headerContainer.Add(new Header(headerLine));
return headerContainer;
}
}
}

767
mime/MimeTypeMap.cs 100644
View File

@ -0,0 +1,767 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ln.http.mime
{
public static class MimeTypeMap
{
private static readonly Lazy<IDictionary<string, string>> _mappings = new Lazy<IDictionary<string, string>>(BuildMappings);
private static IDictionary<string, string> BuildMappings()
{
var mappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
#region Big freaking list of mime types
// maps both ways,
// extension -> mime type
// and
// mime type -> extension
//
// any mime types on left side not pre-loaded on right side, are added automatically
// some mime types can map to multiple extensions, so to get a deterministic mapping,
// add those to the dictionary specifcially
//
// combination of values from Windows 7 Registry and
// from C:\Windows\System32\inetsrv\config\applicationHost.config
// some added, including .7z and .dat
//
// Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml
// which lists mime types, but not extensions
//
{".323", "text/h323"},
{".3g2", "video/3gpp2"},
{".3gp", "video/3gpp"},
{".3gp2", "video/3gpp2"},
{".3gpp", "video/3gpp"},
{".7z", "application/x-7z-compressed"},
{".aa", "audio/audible"},
{".AAC", "audio/aac"},
{".aaf", "application/octet-stream"},
{".aax", "audio/vnd.audible.aax"},
{".ac3", "audio/ac3"},
{".aca", "application/octet-stream"},
{".accda", "application/msaccess.addin"},
{".accdb", "application/msaccess"},
{".accdc", "application/msaccess.cab"},
{".accde", "application/msaccess"},
{".accdr", "application/msaccess.runtime"},
{".accdt", "application/msaccess"},
{".accdw", "application/msaccess.webapplication"},
{".accft", "application/msaccess.ftemplate"},
{".acx", "application/internet-property-stream"},
{".AddIn", "text/xml"},
{".ade", "application/msaccess"},
{".adobebridge", "application/x-bridge-url"},
{".adp", "application/msaccess"},
{".ADT", "audio/vnd.dlna.adts"},
{".ADTS", "audio/aac"},
{".afm", "application/octet-stream"},
{".ai", "application/postscript"},
{".aif", "audio/aiff"},
{".aifc", "audio/aiff"},
{".aiff", "audio/aiff"},
{".air", "application/vnd.adobe.air-application-installer-package+zip"},
{".amc", "application/mpeg"},
{".anx", "application/annodex"},
{".apk", "application/vnd.android.package-archive" },
{".application", "application/x-ms-application"},
{".art", "image/x-jg"},
{".asa", "application/xml"},
{".asax", "application/xml"},
{".ascx", "application/xml"},
{".asd", "application/octet-stream"},
{".asf", "video/x-ms-asf"},
{".ashx", "application/xml"},
{".asi", "application/octet-stream"},
{".asm", "text/plain"},
{".asmx", "application/xml"},
{".aspx", "application/xml"},
{".asr", "video/x-ms-asf"},
{".asx", "video/x-ms-asf"},
{".atom", "application/atom+xml"},
{".au", "audio/basic"},
{".avi", "video/x-msvideo"},
{".axa", "audio/annodex"},
{".axs", "application/olescript"},
{".axv", "video/annodex"},
{".bas", "text/plain"},
{".bcpio", "application/x-bcpio"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".cab", "application/octet-stream"},
{".caf", "audio/x-caf"},
{".calx", "application/vnd.ms-office.calx"},
{".cat", "application/vnd.ms-pki.seccat"},
{".cc", "text/plain"},
{".cd", "text/plain"},
{".cdda", "audio/aiff"},
{".cdf", "application/x-cdf"},
{".cer", "application/x-x509-ca-cert"},
{".cfg", "text/plain"},
{".chm", "application/octet-stream"},
{".class", "application/x-java-applet"},
{".clp", "application/x-msclip"},
{".cmd", "text/plain"},
{".cmx", "image/x-cmx"},
{".cnf", "text/plain"},
{".cod", "image/cis-cod"},
{".config", "application/xml"},
{".contact", "text/x-ms-contact"},
{".coverage", "application/xml"},
{".cpio", "application/x-cpio"},
{".cpp", "text/plain"},
{".crd", "application/x-mscardfile"},
{".crl", "application/pkix-crl"},
{".crt", "application/x-x509-ca-cert"},
{".cs", "text/plain"},
{".csdproj", "text/plain"},
{".csh", "application/x-csh"},
{".csproj", "text/plain"},
{".css", "text/css"},
{".csv", "text/csv"},
{".cur", "application/octet-stream"},
{".cxx", "text/plain"},
{".dat", "application/octet-stream"},
{".datasource", "application/xml"},
{".dbproj", "text/plain"},
{".dcr", "application/x-director"},
{".def", "text/plain"},
{".deploy", "application/octet-stream"},
{".der", "application/x-x509-ca-cert"},
{".dgml", "application/xml"},
{".dib", "image/bmp"},
{".dif", "video/x-dv"},
{".dir", "application/x-director"},
{".disco", "text/xml"},
{".divx", "video/divx"},
{".dll", "application/x-msdownload"},
{".dll.config", "text/xml"},
{".dlm", "text/dlm"},
{".doc", "application/msword"},
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".dot", "application/msword"},
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
{".dsp", "application/octet-stream"},
{".dsw", "text/plain"},
{".dtd", "text/xml"},
{".dtsConfig", "text/xml"},
{".dv", "video/x-dv"},
{".dvi", "application/x-dvi"},
{".dwf", "drawing/x-dwf"},
{".dwg", "application/acad"},
{".dwp", "application/octet-stream"},
{".dxf", "application/x-dxf" },
{".dxr", "application/x-director"},
{".eml", "message/rfc822"},
{".emz", "application/octet-stream"},
{".eot", "application/vnd.ms-fontobject"},
{".eps", "application/postscript"},
{".es", "application/ecmascript"},
{".etl", "application/etl"},
{".etx", "text/x-setext"},
{".evy", "application/envoy"},
{".exe", "application/vnd.microsoft.portable-executable"},
{".exe.config", "text/xml"},
{".f4v", "video/mp4"},
{".fdf", "application/vnd.fdf"},
{".fif", "application/fractals"},
{".filters", "application/xml"},
{".fla", "application/octet-stream"},
{".flac", "audio/flac"},
{".flr", "x-world/x-vrml"},
{".flv", "video/x-flv"},
{".fsscript", "application/fsharp-script"},
{".fsx", "application/fsharp-script"},
{".generictest", "application/xml"},
{".gif", "image/gif"},
{".gpx", "application/gpx+xml"},
{".group", "text/x-ms-group"},
{".gsm", "audio/x-gsm"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".hdf", "application/x-hdf"},
{".hdml", "text/x-hdml"},
{".hhc", "application/x-oleobject"},
{".hhk", "application/octet-stream"},
{".hhp", "application/octet-stream"},
{".hlp", "application/winhlp"},
{".hpp", "text/plain"},
{".hqx", "application/mac-binhex40"},
{".hta", "application/hta"},
{".htc", "text/x-component"},
{".htm", "text/html"},
{".html", "text/html"},
{".htt", "text/webviewhtml"},
{".hxa", "application/xml"},
{".hxc", "application/xml"},
{".hxd", "application/octet-stream"},
{".hxe", "application/xml"},
{".hxf", "application/xml"},
{".hxh", "application/octet-stream"},
{".hxi", "application/octet-stream"},
{".hxk", "application/xml"},
{".hxq", "application/octet-stream"},
{".hxr", "application/octet-stream"},
{".hxs", "application/octet-stream"},
{".hxt", "text/html"},
{".hxv", "application/xml"},
{".hxw", "application/octet-stream"},
{".hxx", "text/plain"},
{".i", "text/plain"},
{".ico", "image/x-icon"},
{".ics", "application/octet-stream"},
{".idl", "text/plain"},
{".ief", "image/ief"},
{".iii", "application/x-iphone"},
{".inc", "text/plain"},
{".inf", "application/octet-stream"},
{".ini", "text/plain"},
{".inl", "text/plain"},
{".ins", "application/x-internet-signup"},
{".ipa", "application/x-itunes-ipa"},
{".ipg", "application/x-itunes-ipg"},
{".ipproj", "text/plain"},
{".ipsw", "application/x-itunes-ipsw"},
{".iqy", "text/x-ms-iqy"},
{".isp", "application/x-internet-signup"},
{".isma", "application/octet-stream"},
{".ismv", "application/octet-stream"},
{".ite", "application/x-itunes-ite"},
{".itlp", "application/x-itunes-itlp"},
{".itms", "application/x-itunes-itms"},
{".itpc", "application/x-itunes-itpc"},
{".IVF", "video/x-ivf"},
{".jar", "application/java-archive"},
{".java", "application/octet-stream"},
{".jck", "application/liquidmotion"},
{".jcz", "application/liquidmotion"},
{".jfif", "image/pjpeg"},
{".jnlp", "application/x-java-jnlp-file"},
{".jpb", "application/octet-stream"},
{".jpe", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/javascript"},
{".json", "application/json"},
{".jsx", "text/jscript"},
{".jsxbin", "text/plain"},
{".latex", "application/x-latex"},
{".library-ms", "application/windows-library+xml"},
{".lit", "application/x-ms-reader"},
{".loadtest", "application/xml"},
{".lpk", "application/octet-stream"},
{".lsf", "video/x-la-asf"},
{".lst", "text/plain"},
{".lsx", "video/x-la-asf"},
{".lzh", "application/octet-stream"},
{".m13", "application/x-msmediaview"},
{".m14", "application/x-msmediaview"},
{".m1v", "video/mpeg"},
{".m2t", "video/vnd.dlna.mpeg-tts"},
{".m2ts", "video/vnd.dlna.mpeg-tts"},
{".m2v", "video/mpeg"},
{".m3u", "audio/x-mpegurl"},
{".m3u8", "audio/x-mpegurl"},
{".m4a", "audio/m4a"},
{".m4b", "audio/m4b"},
{".m4p", "audio/m4p"},
{".m4r", "audio/x-m4r"},
{".m4v", "video/x-m4v"},
{".mac", "image/x-macpaint"},
{".mak", "text/plain"},
{".man", "application/x-troff-man"},
{".manifest", "application/x-ms-manifest"},
{".map", "text/plain"},
{".master", "application/xml"},
{".mbox", "application/mbox"},
{".mda", "application/msaccess"},
{".mdb", "application/x-msaccess"},
{".mde", "application/msaccess"},
{".mdp", "application/octet-stream"},
{".me", "application/x-troff-me"},
{".mfp", "application/x-shockwave-flash"},
{".mht", "message/rfc822"},
{".mhtml", "message/rfc822"},
{".mid", "audio/mid"},
{".midi", "audio/mid"},
{".mix", "application/octet-stream"},
{".mk", "text/plain"},
{".mk3d", "video/x-matroska-3d"},
{".mka", "audio/x-matroska"},
{".mkv", "video/x-matroska"},
{".mmf", "application/x-smaf"},
{".mno", "text/xml"},
{".mny", "application/x-msmoney"},
{".mod", "video/mpeg"},
{".mov", "video/quicktime"},
{".movie", "video/x-sgi-movie"},
{".mp2", "video/mpeg"},
{".mp2v", "video/mpeg"},
{".mp3", "audio/mpeg"},
{".mp4", "video/mp4"},
{".mp4v", "video/mp4"},
{".mpa", "video/mpeg"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpf", "application/vnd.ms-mediapackage"},
{".mpg", "video/mpeg"},
{".mpp", "application/vnd.ms-project"},
{".mpv2", "video/mpeg"},
{".mqv", "video/quicktime"},
{".ms", "application/x-troff-ms"},
{".msg", "application/vnd.ms-outlook"},
{".msi", "application/octet-stream"},
{".mso", "application/octet-stream"},
{".mts", "video/vnd.dlna.mpeg-tts"},
{".mtx", "application/xml"},
{".mvb", "application/x-msmediaview"},
{".mvc", "application/x-miva-compiled"},
{".mxf", "application/mxf"},
{".mxp", "application/x-mmxp"},
{".nc", "application/x-netcdf"},
{".nsc", "video/x-ms-asf"},
{".nws", "message/rfc822"},
{".ocx", "application/octet-stream"},
{".oda", "application/oda"},
{".odb", "application/vnd.oasis.opendocument.database"},
{".odc", "application/vnd.oasis.opendocument.chart"},
{".odf", "application/vnd.oasis.opendocument.formula"},
{".odg", "application/vnd.oasis.opendocument.graphics"},
{".odh", "text/plain"},
{".odi", "application/vnd.oasis.opendocument.image"},
{".odl", "text/plain"},
{".odm", "application/vnd.oasis.opendocument.text-master"},
{".odp", "application/vnd.oasis.opendocument.presentation"},
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
{".odt", "application/vnd.oasis.opendocument.text"},
{".oga", "audio/ogg"},
{".ogg", "audio/ogg"},
{".ogv", "video/ogg"},
{".ogx", "application/ogg"},
{".one", "application/onenote"},
{".onea", "application/onenote"},
{".onepkg", "application/onenote"},
{".onetmp", "application/onenote"},
{".onetoc", "application/onenote"},
{".onetoc2", "application/onenote"},
{".opus", "audio/ogg"},
{".orderedtest", "application/xml"},
{".osdx", "application/opensearchdescription+xml"},
{".otf", "application/font-sfnt"},
{".otg", "application/vnd.oasis.opendocument.graphics-template"},
{".oth", "application/vnd.oasis.opendocument.text-web"},
{".otp", "application/vnd.oasis.opendocument.presentation-template"},
{".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
{".ott", "application/vnd.oasis.opendocument.text-template"},
{".oxt", "application/vnd.openofficeorg.extension"},
{".p10", "application/pkcs10"},
{".p12", "application/x-pkcs12"},
{".p7b", "application/x-pkcs7-certificates"},
{".p7c", "application/pkcs7-mime"},
{".p7m", "application/pkcs7-mime"},
{".p7r", "application/x-pkcs7-certreqresp"},
{".p7s", "application/pkcs7-signature"},
{".pbm", "image/x-portable-bitmap"},
{".pcast", "application/x-podcast"},
{".pct", "image/pict"},
{".pcx", "application/octet-stream"},
{".pcz", "application/octet-stream"},
{".pdf", "application/pdf"},
{".pfb", "application/octet-stream"},
{".pfm", "application/octet-stream"},
{".pfx", "application/x-pkcs12"},
{".pgm", "image/x-portable-graymap"},
{".pic", "image/pict"},
{".pict", "image/pict"},
{".pkgdef", "text/plain"},
{".pkgundef", "text/plain"},
{".pko", "application/vnd.ms-pki.pko"},
{".pls", "audio/scpls"},
{".pma", "application/x-perfmon"},
{".pmc", "application/x-perfmon"},
{".pml", "application/x-perfmon"},
{".pmr", "application/x-perfmon"},
{".pmw", "application/x-perfmon"},
{".png", "image/png"},
{".pnm", "image/x-portable-anymap"},
{".pnt", "image/x-macpaint"},
{".pntg", "image/x-macpaint"},
{".pnz", "image/png"},
{".pot", "application/vnd.ms-powerpoint"},
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
{".ppa", "application/vnd.ms-powerpoint"},
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
{".ppm", "image/x-portable-pixmap"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prf", "application/pics-rules"},
{".prm", "application/octet-stream"},
{".prx", "application/octet-stream"},
{".ps", "application/postscript"},
{".psc1", "application/PowerShell"},
{".psd", "application/octet-stream"},
{".psess", "application/xml"},
{".psm", "application/octet-stream"},
{".psp", "application/octet-stream"},
{".pst", "application/vnd.ms-outlook"},
{".pub", "application/x-mspublisher"},
{".pwz", "application/vnd.ms-powerpoint"},
{".qht", "text/x-html-insertion"},
{".qhtm", "text/x-html-insertion"},
{".qt", "video/quicktime"},
{".qti", "image/x-quicktime"},
{".qtif", "image/x-quicktime"},
{".qtl", "application/x-quicktimeplayer"},
{".qxd", "application/octet-stream"},
{".ra", "audio/x-pn-realaudio"},
{".ram", "audio/x-pn-realaudio"},
{".rar", "application/x-rar-compressed"},
{".ras", "image/x-cmu-raster"},
{".rat", "application/rat-file"},
{".rc", "text/plain"},
{".rc2", "text/plain"},
{".rct", "text/plain"},
{".rdlc", "application/xml"},
{".reg", "text/plain"},
{".resx", "application/xml"},
{".rf", "image/vnd.rn-realflash"},
{".rgb", "image/x-rgb"},
{".rgs", "text/plain"},
{".rm", "application/vnd.rn-realmedia"},
{".rmi", "audio/mid"},
{".rmp", "application/vnd.rn-rn_music_package"},
{".roff", "application/x-troff"},
{".rpm", "audio/x-pn-realaudio-plugin"},
{".rqy", "text/x-ms-rqy"},
{".rtf", "application/rtf"},
{".rtx", "text/richtext"},
{".rvt", "application/octet-stream" },
{".ruleset", "application/xml"},
{".s", "text/plain"},
{".safariextz", "application/x-safari-safariextz"},
{".scd", "application/x-msschedule"},
{".scr", "text/plain"},
{".sct", "text/scriptlet"},
{".sd2", "audio/x-sd2"},
{".sdp", "application/sdp"},
{".sea", "application/octet-stream"},
{".searchConnector-ms", "application/windows-search-connector+xml"},
{".setpay", "application/set-payment-initiation"},
{".setreg", "application/set-registration-initiation"},
{".settings", "application/xml"},
{".sgimb", "application/x-sgimb"},
{".sgml", "text/sgml"},
{".sh", "application/x-sh"},
{".shar", "application/x-shar"},
{".shtml", "text/html"},
{".sit", "application/x-stuffit"},
{".sitemap", "application/xml"},
{".skin", "application/xml"},
{".skp", "application/x-koan" },
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
{".slk", "application/vnd.ms-excel"},
{".sln", "text/plain"},
{".slupkg-ms", "application/x-ms-license"},
{".smd", "audio/x-smd"},
{".smi", "application/octet-stream"},
{".smx", "audio/x-smd"},
{".smz", "audio/x-smd"},
{".snd", "audio/basic"},
{".snippet", "application/xml"},
{".snp", "application/octet-stream"},
{".sol", "text/plain"},
{".sor", "text/plain"},
{".spc", "application/x-pkcs7-certificates"},
{".spl", "application/futuresplash"},
{".spx", "audio/ogg"},
{".src", "application/x-wais-source"},
{".srf", "text/plain"},
{".SSISDeploymentManifest", "text/xml"},
{".ssm", "application/streamingmedia"},
{".sst", "application/vnd.ms-pki.certstore"},
{".stl", "application/vnd.ms-pki.stl"},
{".sv4cpio", "application/x-sv4cpio"},
{".sv4crc", "application/x-sv4crc"},
{".svc", "application/xml"},
{".svg", "image/svg+xml"},
{".swf", "application/x-shockwave-flash"},
{".step", "application/step"},
{".stp", "application/step"},
{".t", "application/x-troff"},
{".tar", "application/x-tar"},
{".tcl", "application/x-tcl"},
{".testrunconfig", "application/xml"},
{".testsettings", "application/xml"},
{".tex", "application/x-tex"},
{".texi", "application/x-texinfo"},
{".texinfo", "application/x-texinfo"},
{".tgz", "application/x-compressed"},
{".thmx", "application/vnd.ms-officetheme"},
{".thn", "application/octet-stream"},
{".tif", "image/tiff"},
{".tiff", "image/tiff"},
{".tlh", "text/plain"},
{".tli", "text/plain"},
{".toc", "application/octet-stream"},
{".tr", "application/x-troff"},
{".trm", "application/x-msterminal"},
{".trx", "application/xml"},
{".ts", "video/vnd.dlna.mpeg-tts"},
{".tsv", "text/tab-separated-values"},
{".ttf", "application/font-sfnt"},
{".tts", "video/vnd.dlna.mpeg-tts"},
{".txt", "text/plain"},
{".u32", "application/octet-stream"},
{".uls", "text/iuls"},
{".user", "text/plain"},
{".ustar", "application/x-ustar"},
{".vb", "text/plain"},
{".vbdproj", "text/plain"},
{".vbk", "video/mpeg"},
{".vbproj", "text/plain"},
{".vbs", "text/vbscript"},
{".vcf", "text/x-vcard"},
{".vcproj", "application/xml"},
{".vcs", "text/plain"},
{".vcxproj", "application/xml"},
{".vddproj", "text/plain"},
{".vdp", "text/plain"},
{".vdproj", "text/plain"},
{".vdx", "application/vnd.ms-visio.viewer"},
{".vml", "text/xml"},
{".vscontent", "application/xml"},
{".vsct", "text/xml"},
{".vsd", "application/vnd.visio"},
{".vsi", "application/ms-vsi"},
{".vsix", "application/vsix"},
{".vsixlangpack", "text/xml"},
{".vsixmanifest", "text/xml"},
{".vsmdi", "application/xml"},
{".vspscc", "text/plain"},
{".vss", "application/vnd.visio"},
{".vsscc", "text/plain"},
{".vssettings", "text/xml"},
{".vssscc", "text/plain"},
{".vst", "application/vnd.visio"},
{".vstemplate", "text/xml"},
{".vsto", "application/x-ms-vsto"},
{".vsw", "application/vnd.visio"},
{".vsx", "application/vnd.visio"},
{".vtt", "text/vtt"},
{".vtx", "application/vnd.visio"},
{".wasm", "application/wasm"},
{".wav", "audio/wav"},
{".wave", "audio/wav"},
{".wax", "audio/x-ms-wax"},
{".wbk", "application/msword"},
{".wbmp", "image/vnd.wap.wbmp"},
{".wcm", "application/vnd.ms-works"},
{".wdb", "application/vnd.ms-works"},
{".wdp", "image/vnd.ms-photo"},
{".webarchive", "application/x-safari-webarchive"},
{".webm", "video/webm"},
{".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */
{".webtest", "application/xml"},
{".wiq", "application/xml"},
{".wiz", "application/msword"},
{".wks", "application/vnd.ms-works"},
{".WLMP", "application/wlmoviemaker"},
{".wlpginstall", "application/x-wlpg-detect"},
{".wlpginstall3", "application/x-wlpg3-detect"},
{".wm", "video/x-ms-wm"},
{".wma", "audio/x-ms-wma"},
{".wmd", "application/x-ms-wmd"},
{".wmf", "application/x-msmetafile"},
{".wml", "text/vnd.wap.wml"},
{".wmlc", "application/vnd.wap.wmlc"},
{".wmls", "text/vnd.wap.wmlscript"},
{".wmlsc", "application/vnd.wap.wmlscriptc"},
{".wmp", "video/x-ms-wmp"},
{".wmv", "video/x-ms-wmv"},
{".wmx", "video/x-ms-wmx"},
{".wmz", "application/x-ms-wmz"},
{".woff", "application/font-woff"},
{".woff2", "application/font-woff2"},
{".wpl", "application/vnd.ms-wpl"},
{".wps", "application/vnd.ms-works"},
{".wri", "application/x-mswrite"},
{".wrl", "x-world/x-vrml"},
{".wrz", "x-world/x-vrml"},
{".wsc", "text/scriptlet"},
{".wsdl", "text/xml"},
{".wvx", "video/x-ms-wvx"},
{".x", "application/directx"},
{".xaf", "x-world/x-vrml"},
{".xaml", "application/xaml+xml"},
{".xap", "application/x-silverlight-app"},
{".xbap", "application/x-ms-xbap"},
{".xbm", "image/x-xbitmap"},
{".xdr", "text/plain"},
{".xht", "application/xhtml+xml"},
{".xhtml", "application/xhtml+xml"},
{".xla", "application/vnd.ms-excel"},
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
{".xlc", "application/vnd.ms-excel"},
{".xld", "application/vnd.ms-excel"},
{".xlk", "application/vnd.ms-excel"},
{".xll", "application/vnd.ms-excel"},
{".xlm", "application/vnd.ms-excel"},
{".xls", "application/vnd.ms-excel"},
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".xlt", "application/vnd.ms-excel"},
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
{".xlw", "application/vnd.ms-excel"},
{".xml", "text/xml"},
{".xmp", "application/octet-stream" },
{".xmta", "application/xml"},
{".xof", "x-world/x-vrml"},
{".XOML", "text/plain"},
{".xpm", "image/x-xpixmap"},
{".xps", "application/vnd.ms-xpsdocument"},
{".xrm-ms", "text/xml"},
{".xsc", "application/xml"},
{".xsd", "text/xml"},
{".xsf", "text/xml"},
{".xsl", "text/xml"},
{".xslt", "text/xml"},
{".xsn", "application/octet-stream"},
{".xss", "application/xml"},
{".xspf", "application/xspf+xml"},
{".xtp", "application/octet-stream"},
{".xwd", "image/x-xwindowdump"},
{".z", "application/x-compress"},
{".zip", "application/zip"},
{"application/fsharp-script", ".fsx"},
{"application/msaccess", ".adp"},
{"application/msword", ".doc"},
{"application/octet-stream", ".bin"},
{"application/onenote", ".one"},
{"application/postscript", ".eps"},
{"application/step", ".step"},
{"application/vnd.ms-excel", ".xls"},
{"application/vnd.ms-powerpoint", ".ppt"},
{"application/vnd.ms-works", ".wks"},
{"application/vnd.visio", ".vsd"},
{"application/x-director", ".dir"},
{"application/x-shockwave-flash", ".swf"},
{"application/x-x509-ca-cert", ".cer"},
{"application/x-zip-compressed", ".zip"},
{"application/xhtml+xml", ".xhtml"},
{"application/xml", ".xml"}, // anomoly, .xml -> text/xml, but application/xml -> many thingss, but all are xml, so safest is .xml
{"audio/aac", ".AAC"},
{"audio/aiff", ".aiff"},
{"audio/basic", ".snd"},
{"audio/mid", ".midi"},
{"audio/wav", ".wav"},
{"audio/x-m4a", ".m4a"},
{"audio/x-mpegurl", ".m3u"},
{"audio/x-pn-realaudio", ".ra"},
{"audio/x-smd", ".smd"},
{"image/bmp", ".bmp"},
{"image/jpeg", ".jpg"},
{"image/pict", ".pic"},
{"image/png", ".png"}, //Defined in [RFC-2045], [RFC-2048]
{"image/x-png", ".png"}, //See https://www.w3.org/TR/PNG/#A-Media-type :"It is recommended that implementations also recognize the media type "image/x-png"."
{"image/tiff", ".tiff"},
{"image/x-macpaint", ".mac"},
{"image/x-quicktime", ".qti"},
{"message/rfc822", ".eml"},
{"text/html", ".html"},
{"text/plain", ".txt"},
{"text/scriptlet", ".wsc"},
{"text/xml", ".xml"},
{"video/3gpp", ".3gp"},
{"video/3gpp2", ".3gp2"},
{"video/mp4", ".mp4"},
{"video/mpeg", ".mpg"},
{"video/quicktime", ".mov"},
{"video/vnd.dlna.mpeg-tts", ".m2t"},
{"video/x-dv", ".dv"},
{"video/x-la-asf", ".lsf"},
{"video/x-ms-asf", ".asf"},
{"x-world/x-vrml", ".xof"},
#endregion
};
var cache = mappings.ToList(); // need ToList() to avoid modifying while still enumerating
foreach (var mapping in cache)
{
if (!mappings.ContainsKey(mapping.Value))
{
mappings.Add(mapping.Value, mapping.Key);
}
}
return mappings;
}
public static string GetMimeType(string extension)
{
if (extension == null)
{
throw new ArgumentNullException("extension");
}
if (!extension.StartsWith("."))
{
extension = "." + extension;
}
string mime;
return _mappings.Value.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
}
public static string GetExtension(string mimeType)
{
return GetExtension(mimeType, true);
}
public static string GetExtension(string mimeType, bool throwErrorIfNotFound)
{
if (mimeType == null)
{
throw new ArgumentNullException("mimeType");
}
if (mimeType.StartsWith("."))
{
throw new ArgumentException("Requested mime type is not valid: " + mimeType);
}
string extension;
if (_mappings.Value.TryGetValue(mimeType, out extension))
{
return extension;
}
if (throwErrorIfNotFound)
{
throw new ArgumentException("Requested mime type is not registered: " + mimeType);
}
else
{
return string.Empty;
}
}
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
namespace ln.http.rest
{
public abstract partial class CRUDObjectContainer
{
class RegisteredType
{
public CRUDObjectContainer ObjectContainer { get; }
public Type ObjectType { get; }
Func<object,object> _idGetter;
Dictionary<object, object> objectCache = new Dictionary<object, object>();
public IEnumerable<FieldDescriptor> FieldDescriptors => fieldDescriptors;
List<FieldDescriptor> fieldDescriptors = new List<FieldDescriptor>();
public RegisteredType(CRUDObjectContainer objectContainer,Type objectType,Func<object,object> idGetter)
{
ObjectContainer = objectContainer;
ObjectType = objectType;
_idGetter = idGetter;
}
public bool TryGetObjectID(object objectInstance,out object objectIdentifier)
{
try
{
objectIdentifier = _idGetter(objectInstance);
} catch (Exception e)
{
objectIdentifier = null;
return false;
}
return true;
}
public void AddCachedObject(object objectInstance)
{
Type objectType = objectInstance.GetType();
if (!ObjectType.Equals(objectType))
throw new ArgumentException();
if (!TryGetObjectID(objectInstance, out object objectIdentifier))
throw new ArgumentOutOfRangeException();
objectCache.Add(objectIdentifier, objectInstance);
}
public void RemoveCachedObject(object objectInstance)
{
Type objectType = objectInstance.GetType();
if (!ObjectType.Equals(objectType))
throw new ArgumentException();
if (!TryGetObjectID(objectInstance, out object objectIdentifier))
throw new ArgumentOutOfRangeException();
objectCache.Remove(objectIdentifier);
}
}
}
}

View File

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ln.http.rest
{
public delegate void CRUDEvent(CRUDObjectContainer sender, CRUDEventArgs args);
public enum CRUDEventType { CREATE, READ, UPDATE, DELETE }
public class CRUDEventArgs : EventArgs
{
public CRUDEventType EventType { get; }
public Type ObjectType { get; }
public object ObjectIdentity { get; }
public object ObjectInstance { get; }
public FieldValueList FieldValues { get; }
public CRUDEventArgs(CRUDEventType eventType, Type objectType, object objectIdentity, object objectInstance, FieldValueList fieldValues)
{
EventType = eventType;
ObjectType = objectType;
ObjectIdentity = objectIdentity;
ObjectInstance = objectInstance;
FieldValues = fieldValues;
}
}
public abstract partial class CRUDObjectContainer
{
public event CRUDEvent OnCRUDEvent;
Dictionary<Type, RegisteredType> registeredTypes = new Dictionary<Type, RegisteredType>();
public CRUDObjectContainer()
{ }
public abstract bool TryGetObjectIdentifier(object objectInstance, out object objectIdentifier);
public abstract bool TryCreateObject(Type objectType, FieldValueList fieldValues, out object objectInstance);
public abstract bool TryLookupObject(Type objectType, object objectIdentifier, out object objectInstance);
public abstract bool TryUpdateObject(Type objectType, object objectIdentifier, FieldValueList fieldValues);
public abstract bool TryDeleteObject(Type objectType, object objectIdentifier);
public virtual bool TryUpdateObject(object objectInstance, FieldValueList fieldValues)
{
if (TryGetObjectIdentifier(objectInstance, out object objectIdentifier))
return TryUpdateObject(objectInstance.GetType(), objectIdentifier, fieldValues);
return false;
}
public virtual bool TryDeleteObject(object objectInstance)
{
if (TryGetObjectIdentifier(objectInstance, out object objectIdentifier))
return TryDeleteObject(objectInstance.GetType(), objectIdentifier);
return false;
}
protected void FireCRUDEvent(CRUDEventArgs args) => OnCRUDEvent?.Invoke(this, args);
protected void FireCRUDEVent(CRUDEventType EventType, Type ObjectType, object ObjectIdentity, object ObjectInstance, FieldValueList FieldValues)
=> FireCRUDEvent(new CRUDEventArgs(EventType, ObjectType, ObjectIdentity, ObjectInstance, FieldValues));
public virtual IEnumerable<Type> RegisteredTypes => registeredTypes.Keys;
public virtual bool ContainsRegisteredType(Type objectType) => registeredTypes.ContainsKey(objectType);
public virtual bool TryRegisterType(Type objectType) => TryRegisterType(objectType, null);
public virtual bool TryRegisterType(Type objectType, Func<object,object> idGetter)
{
if (ContainsRegisteredType(objectType))
throw new ArgumentException("objectType already registered", nameof(objectType));
RegisteredType registeredType = new RegisteredType(this, objectType, idGetter);
registeredTypes.Add(objectType, registeredType);
return true;
}
public virtual bool TryGetFieldDescriptors(Type objectType, out FieldDescriptor[] fieldDescriptors)
{
if (registeredTypes.TryGetValue(objectType, out RegisteredType registeredType))
{
fieldDescriptors = registeredType.FieldDescriptors.ToArray();
return true;
}
fieldDescriptors = null;
return false;
}
public virtual IEnumerable<object> EnumerateObjectInstances(Type objectType) => EnumerateObjectInstances(objectType, null);
public abstract IEnumerable<object> EnumerateObjectInstances(Type objectType, FieldValueList conditionalFieldValues);
public abstract bool AddForeignObjectInstance(object objectInstance);
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace ln.http.rest
{
public class FieldDescriptor
{
Action<object, object> setter;
Func<object, object> getter;
public Type OwningType { get; }
public string FieldName { get; }
public Type FieldType { get; }
public bool CanWrite { get; }
public FieldDescriptor(FieldInfo fieldInfo)
{
OwningType = fieldInfo.DeclaringType;
FieldName = fieldInfo.Name;
FieldType = fieldInfo.FieldType;
CanWrite = !fieldInfo.IsInitOnly;
getter = fieldInfo.GetValue;
setter = fieldInfo.SetValue;
}
public FieldDescriptor(PropertyInfo propertyInfo)
{
OwningType = propertyInfo.DeclaringType;
FieldName = propertyInfo.Name;
FieldType = propertyInfo.PropertyType;
CanWrite = propertyInfo.CanWrite;
getter = propertyInfo.GetValue;
setter = propertyInfo.SetValue;
}
public void SetFieldValue(object objectInstance, object fieldValue) => setter(objectInstance, fieldValue);
public object GetFieldValue(object objectInstance) => getter(objectInstance);
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace ln.http.rest
{
public class FieldValueList
{
public Type ObjectType { get; }
Dictionary<string, object> fieldValues = new Dictionary<string, object>();
public FieldValueList(Type objectType)
{
ObjectType = objectType;
}
public IEnumerable<string> FieldNames => fieldValues.Keys;
public object GetFieldValue(string fieldName) => fieldValues[fieldName];
public void SetFieldValue(string fieldName, object fieldValue) => fieldValues[fieldName] = fieldValue;
public void RemoveFieldValue(string fieldName) => fieldValues.Remove(fieldName);
}
}

View File

@ -0,0 +1,22 @@
using ln.http.router;
using System;
using System.Collections.Generic;
using System.Text;
namespace ln.http.rest
{
public class RestAPIAdapter : IHttpRouter
{
public CRUDObjectContainer ObjectContainer { get; }
public RestAPIAdapter(CRUDObjectContainer objectContainer)
{
ObjectContainer = objectContainer;
}
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,35 @@
// /**
// * File: FileSystemRouter.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System.IO;
using ln.http.message;
namespace ln.http.router
{
public class FileRouter : IHttpRouter
{
public string FileName { get; set; }
public FileRouter(string filename)
{
if (!File.Exists(filename))
throw new FileNotFoundException();
FileName = filename;
}
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
HttpResponse httpResponse = new HttpResponse(httpRequest, new FileStream(FileName, FileMode.Open));
httpResponse.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(FileName)));
return httpResponse;
}
}
}

View File

@ -0,0 +1,53 @@
using System;
namespace ln.http.router
{
public class HttpRoutingContext
{
public HttpRequest HttpRequest { get; }
public string Path { get; set; }
public string RoutedPath { get; set; }
public HttpRoutingContext(HttpRequest httpRequest) : this(httpRequest, httpRequest.URI.AbsolutePath) { }
public HttpRoutingContext(HttpRequest httpRequest, string path)
{
HttpRequest = httpRequest;
Path = path;
RoutedPath = "";
}
HttpRoutingContext(HttpRequest httpRequest,string path,string routedPath)
{
HttpRequest = httpRequest;
Path = path;
RoutedPath = routedPath;
}
public HttpRoutingContext Routed(string residual)
{
return new HttpRoutingContext(HttpRequest, residual, RoutedPath + Path.Substring(0,Path.Length - residual.Length));
}
public bool PopNext(out string next, out HttpRoutingContext nextRoutingContext)
{
next = Path;
if (String.Empty.Equals(next) || "/".Equals(next))
{
nextRoutingContext = null;
return false;
}
int indSlash = next.IndexOf('/',1);
if (indSlash > 0)
next = next.Substring(0, indSlash-1);
nextRoutingContext = Routed(Path.Substring(next.Length));
if (next[0] == '/')
next = next.Substring(1);
return true;
}
}
}

View File

@ -0,0 +1,59 @@
// /**
// * File: LoggingRouter.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using ln.logging;
using System.Diagnostics;
namespace ln.http.router
{
public class LoggingRouter : IHttpRouter
{
IHttpRouter Next { get; }
Logger Logger { get; }
public LoggingRouter(IHttpRouter next)
:this(next, Logger.Default)
{}
public LoggingRouter(IHttpRouter next,Logger logger)
{
Next = next;
Logger = logger;
}
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
DateTime start = DateTime.Now;
HttpResponse response = null;
try
{
response = Next.Route(routingContext, httpRequest);
}
catch (Exception e)
{
throw;
}
finally
{
DateTime end = DateTime.Now;
TimeSpan duration = end - start;
Logger.Log(LogLevel.INFO, "{0} {1} {2} {3} {4}",
start,
duration,
response?.StatusCode.ToString() ?? "-",
httpRequest.Method,
httpRequest.RequestURL
);
}
return response;
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using ln.http.exceptions;
namespace ln.http.router
{
public class RouterTarget :IHttpRouter
{
public Func<HttpRoutingContext,HttpRequest, HttpResponse> Target { get; }
public RouterTarget(Func<string, HttpRequest, HttpResponse> target)
{
Target = (HttpRoutingContext routingContext, HttpRequest httpRequest) => target(routingContext.Path, httpRequest);
}
public RouterTarget(Func<HttpRoutingContext, HttpRequest, HttpResponse> target)
{
Target = target;
}
public RouterTarget(Func<HttpRequest, HttpResponse> target)
{
Target = (context,request) => target(request);
}
protected RouterTarget()
{
Target = (path,request) => Dispatch(request);
}
public virtual HttpResponse Dispatch(HttpRequest request)
{
switch (request.Method.ToUpper())
{
case "HEAD":
return HEAD(request);
case "GET":
return GET(request);
case "PROPFIND":
return PROPFIND(request);
case "POST":
return POST(request);
case "PUT":
return PUT(request);
case "DELETE":
return DELETE(request);
default:
throw new MethodNotAllowedException();
}
}
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
return Target(routingContext, httpRequest);
}
public virtual HttpResponse HEAD(HttpRequest request) => throw new MethodNotAllowedException();
public virtual HttpResponse GET(HttpRequest request) => throw new MethodNotAllowedException();
public virtual HttpResponse PROPFIND(HttpRequest request) => throw new MethodNotAllowedException();
public virtual HttpResponse POST(HttpRequest request) => throw new MethodNotAllowedException();
public virtual HttpResponse PUT(HttpRequest request) => throw new MethodNotAllowedException();
public virtual HttpResponse DELETE(HttpRequest request) => throw new MethodNotAllowedException();
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace ln.http.router
{
public class SimpleRouter : IHttpRouter
{
List<SimpleRoute> routes = new List<SimpleRoute>();
public SimpleRoute[] Routes => routes.ToArray();
public SimpleRouter()
{
}
public void AddSimpleRoute(string simpleRoute, Func<HttpRoutingContext, HttpRequest, HttpResponse> target) => AddSimpleRoute(simpleRoute, new RouterTarget(target));
public void AddSimpleRoute(string simpleRoute, Func<string, HttpRequest, HttpResponse> target) => AddSimpleRoute(simpleRoute, new RouterTarget(target));
public void AddSimpleRoute(string simpleRoute, Func<HttpRequest, HttpResponse> target) => AddSimpleRoute(simpleRoute, new RouterTarget(target));
public void AddSimpleRoute(string simpleRoute, IHttpRouter target) => AddSimpleRoute(simpleRoute, target, simpleRoute.Split('/').Length);
public void AddSimpleRoute(string simpleRoute, IHttpRouter target, int priority)
{
string[] parts = simpleRoute.Split(new char[] { '/' });
string[] reparts = parts.Select((part) =>
{
if (part.StartsWith(":", StringComparison.InvariantCulture))
if (part.EndsWith("*", StringComparison.InvariantCulture))
return string.Format("(?<{0}>[^/]+)(?<_>/.*)?", part.Substring(1, part.Length - 2));
else
return string.Format("(?<{0}>[^/]+)", part.Substring(1));
else if (part.Equals("*"))
return string.Format("(?<_>.*)");
else
return string.Format("{0}", part);
}).ToArray();
string reroute = string.Join("/", reparts);
AddRoute(reroute, target, priority);
}
public void AddRoute(String route, IHttpRouter target) => AddRoute(route, target, 0);
public void AddRoute(String route, IHttpRouter target,int priority)
{
lock (this)
{
routes.Add(new SimpleRoute(route, target, priority));
routes.Sort((SimpleRoute a, SimpleRoute b) => b.Priority - a.Priority);
}
}
public void Remove(SimpleRoute simpleRoute) => routes.Remove(simpleRoute);
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
foreach (SimpleRoute simpleRoute in routes.ToArray())
{
Match match = simpleRoute.Route.Match(routingContext.Path);
if (match.Success)
{
string residual = "";
foreach (Group group in match.Groups)
{
httpRequest?.SetParameter(group.Name, group.Value);
if (group.Name.Equals("_"))
if (group.Value.StartsWith("/", StringComparison.InvariantCulture))
residual = group.Value;
else
residual = "/" + group.Value;
}
HttpResponse response = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest);
if (response != null)
return response;
}
}
return null;
}
public class SimpleRoute
{
public int Priority { get; }
public Regex Route { get; }
public IHttpRouter Target { get; }
public SimpleRoute(string regex, IHttpRouter target) : this(regex, target, 0) { }
public SimpleRoute(string regex, IHttpRouter target,int priority)
{
Route = new Regex(regex);
Target = target;
Priority = priority;
}
}
}
}

View File

@ -0,0 +1,67 @@
// /**
// * File: FileSystemRouter.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using ln.http.message;
using System.Collections.Generic;
namespace ln.http.router
{
public class StaticRouter : IHttpRouter
{
public String RootPath { get; }
List<string> indexNames = new List<string>();
public String[] IndexNames => indexNames.ToArray();
public StaticRouter(string path)
{
if (!Directory.Exists(path))
throw new FileNotFoundException();
RootPath = Path.GetFullPath(path);
AddIndex("index.html");
AddIndex("index.htm");
}
public void AddIndex(string indexName) => indexNames.Add(indexName);
public void RemoveIndex(string indexName) => indexNames.Remove(indexName);
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
string finalPath = Path.Combine(RootPath, routingContext.Path.Substring(1));
if (Directory.Exists(finalPath))
{
foreach (string indexName in indexNames)
{
string indexFileName = Path.Combine(finalPath, indexName);
if (File.Exists(indexFileName))
{
finalPath = indexFileName;
break;
}
}
}
if (File.Exists(finalPath))
{
lock (this)
{
HttpResponse httpResponse = new HttpResponse(httpRequest, new FileStream(finalPath, FileMode.Open));
httpResponse.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(finalPath)));
return httpResponse;
}
}
return null;
}
}
}

View File

@ -0,0 +1,48 @@
using ln.http.exceptions;
using System.Collections.Generic;
namespace ln.http.router
{
public class VirtualHostRouter : IHttpRouter
{
public IHttpRouter DefaultRoute { get; set; }
Dictionary<string, IHttpRouter> virtualHosts = new Dictionary<string, IHttpRouter>();
public VirtualHostRouter()
{
}
public VirtualHostRouter(IHttpRouter defaultRoute)
{
DefaultRoute = defaultRoute;
}
public VirtualHostRouter(IEnumerable<KeyValuePair<string, IHttpRouter>> routes)
{
foreach (KeyValuePair<string, IHttpRouter> route in routes)
virtualHosts.Add(route.Key, route.Value);
}
public VirtualHostRouter(IHttpRouter defaultRoute,IEnumerable<KeyValuePair<string, IHttpRouter>> routes)
:this(routes)
{
DefaultRoute = defaultRoute;
}
public VirtualHostRouter(VirtualHostRouter source)
: this(source.virtualHosts) { }
public void Add(string hostname,IHttpRouter router) => virtualHosts.Add(hostname, router);
public void Remove(string hostname) => virtualHosts.Remove(hostname);
public bool Contains(string hostname) => virtualHosts.ContainsKey(hostname);
public bool TryGetValue(string hostname, out IHttpRouter router) => virtualHosts.TryGetValue(hostname, out router);
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
if (virtualHosts.TryGetValue(httpRequest.Hostname, out IHttpRouter virtualHost))
return virtualHost.Route(routingContext, httpRequest);
if (DefaultRoute != null)
return DefaultRoute.Route(routingContext, httpRequest);
throw new HttpException(410, string.Format("Gone. Hostname {0} not found on this server.", httpRequest.Hostname));
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using ln.http.websocket;
using ln.http.exceptions;
using ln.logging;
namespace ln.http.router
{
public class WebsocketRouter : IHttpRouter
{
Func<HttpRequest, WebSocket> createWebsocket;
public WebsocketRouter(Func<HttpRequest, WebSocket> createWebsocketDelegate)
{
createWebsocket = createWebsocketDelegate;
}
public WebSocket CreateWebSocket(HttpRequest request) => createWebsocket(request);
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest)
{
WebSocket websocket = CreateWebSocket(httpRequest);
try
{
websocket.Run();
}
catch (Exception e)
{
Logging.Log(e);
}
throw new DisposeConnectionException();
}
}
}

69
session/Session.cs 100644
View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ln.http.session
{
public class Session : IDisposable
{
public Guid SessionID { get; private set; }
public long SessionAge => DateTimeOffset.Now.ToUnixTimeSeconds() - LastTouch;
public long LastTouch { get; private set; }
public HttpUser CurrentUser { get; set; }
private readonly Dictionary<string, object> elements = new Dictionary<string, object>();
public Session()
{
SessionID = Guid.NewGuid();
CurrentUser = new HttpUser();
}
public object this[string name]
{
get => this.elements[name];
set => this.elements[name] = value;
}
public T Get<T>(string name) where T:class
{
if (elements.ContainsKey(name))
return elements[name] as T;
return null;
}
public String[] ElementNames => this.elements.Keys.ToArray();
public void Touch()
{
LastTouch = DateTimeOffset.Now.ToUnixTimeSeconds();
}
public virtual void Authenticate(HttpRequest httpRequest)
{
}
public void Dispose()
{
foreach (object o in elements.Values)
{
// if (o is IDisposable disposable)
// {
// try
// {
// disposable.Dispose();
// }
//#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
// catch (Exception)
//#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
// {
// }
//}
}
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
namespace ln.http.session
{
public class SessionCache
{
Dictionary<Guid, Session> sessions = new Dictionary<Guid, Session>();
public SessionCache()
{
}
public bool Contains(Guid sessionID)
{
return this.sessions.ContainsKey(sessionID);
}
/*
* Create a new Session instance
*
*/
public virtual Session CreateSession()
{
return new Session();
}
/*
* Find and return SessionID from given HttpRequest
*
*/
public virtual Guid FindSessionID(HttpRequest httpRequest)
{
if (httpRequest.ContainsCookie("SID_LN"))
{
return Guid.Parse(httpRequest.GetCookie("SID_LN"));
}
return Guid.Empty;
}
/*
* Apply SessionID of the given Session to the given Response
*
*/
public virtual void ApplySessionID(HttpResponse httpResponse,Session session)
{
HttpCookie sessionCookie = new HttpCookie("SID_LN",session.SessionID.ToString());
sessionCookie.Path = "/";
httpResponse.AddCookie(sessionCookie);
}
public Session GetSession(HttpRequest httpRequest)
{
Guid sessionID = FindSessionID(httpRequest);
if (!Guid.Empty.Equals(sessionID) && Contains(sessionID))
{
lock (sessions)
{
Session session = this.sessions[sessionID];
session.Touch();
return session;
}
}
else
{
Session session = CreateSession();
lock (this.sessions)
{
this.sessions.Add(session.SessionID, session);
}
return session;
}
}
}
}

View File

@ -0,0 +1,201 @@
using System;
using System.IO;
using System.Threading;
using ln.logging;
using ln.http.exceptions;
using System.Security.Cryptography;
using System.Text;
using ln.type;
using ln.http.connections;
namespace ln.http.websocket
{
public enum WebSocketOpcode : int
{
CONTINUATION = 0x00,
TEXT = 0x01,
BINARY = 0x02,
CLOSE = 0x08,
PING = 0x09,
PONG = 0x0A,
INVALIDOPCODE = -1
}
public enum WebSocketState
{
HANDSHAKE,
OPEN,
CLOSING,
CLOSED,
ERROR
}
public delegate void WebSocketEventDelegate(WebSocket sender,WebSocketEventArgs e);
public abstract class WebSocket
{
public HTTPServer HTTPServer => HttpRequest.HTTPServer;
public HttpRequest HttpRequest { get; }
public Stream Stream { get; }
public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE;
public WebSocket(HttpRequest httpRequest)
{
HttpRequest = httpRequest;
Stream = httpRequest.GetConnectionStream();
if ((!httpRequest.GetRequestHeader("upgrade", "").Contains("websocket")) && (!httpRequest.GetRequestHeader("connection", "").Contains("Upgrade")))
throw new HttpException(400, "This resource is a websocket endpoint only");
if (!httpRequest.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13"))
throw new HttpException(400, "Unsupported Protocol Version (WebSocket)");
String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key");
HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 101;
httpResponse.StatusMessage = "Switching Protocols";
httpResponse.AddHeader("upgrade", "websocket");
httpResponse.AddHeader("connection", "Upgrade");
httpResponse.AddHeader("Sec-WebSocket-Version", "13");
string acceptKey = String.Format("{0}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", httpRequest.GetRequestHeader("Sec-WebSocket-Key"));
httpResponse.AddHeader(
"Sec-Websocket-Accept",
Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey)))
);
Connection.SendResponse(Stream, httpResponse);
//HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close();
State = WebSocketState.OPEN;
}
public bool IsAlive => false;
public void Close()
{
switch (State)
{
case WebSocketState.HANDSHAKE:
case WebSocketState.ERROR:
case WebSocketState.CLOSING:
State = WebSocketState.CLOSED;
Stream.Close();
break;
case WebSocketState.CLOSED:
break;
case WebSocketState.OPEN:
WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE);
lock (Stream)
{
Send(closeFrame);
State = WebSocketState.CLOSING;
}
break;
}
}
public void Run()
{
try
{
while (State != WebSocketState.CLOSED)
{
WebSocketFrame webSocketFrame = new WebSocketFrame(Stream);
switch (webSocketFrame.Opcode)
{
case WebSocketOpcode.TEXT:
Received(Encoding.UTF8.GetString(webSocketFrame.ApplicationData));
break;
case WebSocketOpcode.BINARY:
Received(webSocketFrame.ApplicationData);
break;
case WebSocketOpcode.CLOSE:
if (State == WebSocketState.OPEN)
{
WebSocketFrame closeFrame = new WebSocketFrame(WebSocketOpcode.CLOSE);
closeFrame.FIN = true;
lock (Stream)
{
Send(closeFrame);
State = WebSocketState.CLOSING;
}
}
State = WebSocketState.CLOSED;
Stream.Close();
break;
case WebSocketOpcode.PING:
WebSocketFrame pong = new WebSocketFrame(WebSocketOpcode.PONG);
pong.ApplicationData = webSocketFrame.ApplicationData;
Send(pong);
break;
}
}
} catch (IOException)
{
State = WebSocketState.ERROR;
Logging.Log(LogLevel.DEBUG, "WebSocket connection was dropped unexpected");
Close();
} catch (Exception e)
{
Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString());
Logging.Log(e);
} finally
{
}
}
public virtual bool Received(string textMessage)
{
Logging.Log(LogLevel.WARNING, "WebSocket received unexpected text message:\n{0}", textMessage);
return false;
}
public virtual bool Received(byte[] binaryMessage)
{
Logging.Log(LogLevel.WARNING, "WebSocket received unexpected binary message:\n{0}",binaryMessage.ToHexString());
return false;
}
public void Send(WebSocketFrame frame)
{
lock (Stream)
{
if (State == WebSocketState.OPEN)
{
try
{
frame.WriteTo(Stream);
} catch (IOException)
{
if (State != WebSocketState.ERROR)
{
Logging.Log(LogLevel.ERROR, "WebSocket.Send(): Websocket connection was dropped unexpected");
State = WebSocketState.ERROR;
Close();
}
}
}
else
throw new IOException("WebSocket is not open");
}
}
public void Send(string textMessage)
{
WebSocketFrame webSocketFrame = new WebSocketFrame(textMessage);
Send(webSocketFrame);
}
public void Send(byte[] binaryMessage)
{
WebSocketFrame webSocketFrame = new WebSocketFrame(binaryMessage);
Send(webSocketFrame);
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Text;
namespace ln.http.websocket
{
public enum WebSocketEventType { OPEN, CLOSE, MESSAGE, ERROR }
public class WebSocketEventArgs
{
public WebSocketFrame Frame { get; }
public WebSocketEventType EventType { get; }
public byte[] BinaryMessage { get; }
public String TextMessage => Encoding.UTF8.GetString(BinaryMessage);
public bool IsBinary { get; }
public String ErrorMessage { get; }
public WebSocketEventArgs(WebSocketFrame frame)
{
Frame = frame;
switch (frame.Opcode)
{
case WebSocketOpcode.BINARY:
case WebSocketOpcode.TEXT:
IsBinary = (frame.Opcode == WebSocketOpcode.BINARY);
EventType = WebSocketEventType.MESSAGE;
BinaryMessage = frame.ApplicationData;
ErrorMessage = null;
break;
}
}
}
}

View File

@ -0,0 +1,171 @@
using System;
using System.IO;
using System.Text;
using ln.type;
using ln.logging;
namespace ln.http.websocket
{
public class WebSocketFrame
{
public bool FIN;
public bool RSV1;
public bool RSV2;
public bool RSV3;
public WebSocketOpcode Opcode = WebSocketOpcode.INVALIDOPCODE;
public bool Mask;
public int MaskingKey;
public byte[] ExtensionData;
public byte[] ApplicationData;
public byte[] Payload => ExtensionData.Concat(ApplicationData);
public WebSocketFrame()
{
ExtensionData = new byte[0];
ApplicationData = new byte[0];
}
public WebSocketFrame(WebSocketOpcode opcode)
:this(opcode,new byte[0])
{
}
public WebSocketFrame(WebSocketOpcode opcode,byte[] applicationData)
{
Opcode = opcode;
ExtensionData = new byte[0];
ApplicationData = applicationData;
}
public WebSocketFrame(string applicationData)
:this(Encoding.UTF8.GetBytes(applicationData),new byte[0])
{
Opcode = WebSocketOpcode.TEXT;
}
public WebSocketFrame(byte[] applicationData)
: this(applicationData, new byte[0]) { }
public WebSocketFrame(byte[] applicationData,byte[] extensionData)
{
FIN = true;
Opcode = WebSocketOpcode.BINARY;
ExtensionData = extensionData;
ApplicationData = applicationData;
}
public WebSocketFrame(Stream stream)
{
ReadFrom(stream);
}
public void ReadFrom(Stream stream)
{
int firstByte = stream.ReadByte();
if (firstByte == -1)
throw new IOException();
FIN = (firstByte & 0x80) != 0;
RSV1 = (firstByte & 0x40) != 0;
RSV3 = (firstByte & 0x20) != 0;
RSV1 = (firstByte & 0x10) != 0;
Opcode = (WebSocketOpcode)(firstByte & 0x0F);
int secondByte = stream.ReadByte();
if (secondByte == -1)
throw new IOException();
Mask = (secondByte & 0x80) != 0;
int pLength = (secondByte) & 0x7F;
if (pLength == 126)
{
pLength = stream.ReadUShort(true);
}
else if (pLength == 127)
{
ulong ulpLength = stream.ReadULong(true);
if (ulpLength > int.MaxValue)
throw new NotSupportedException(String.Format("Maximum supported frame size is: {0} bytes", int.MaxValue));
pLength = (int)ulpLength;
}
if (Mask)
{
MaskingKey = stream.ReadInteger();
}
ExtensionData = new byte[0];
ApplicationData = stream.ReadBytes(pLength);
if (Mask)
{
MaskPayload(ApplicationData, MaskingKey);
}
}
public void WriteTo(Stream stream)
{
stream.WriteByte(
(byte)(
(FIN ? 0x80 : 0x00) |
(RSV1 ? 0x40 : 0x00) |
(RSV2 ? 0x20 : 0x00) |
(RSV3 ? 0x10 : 0x00) |
(((int)Opcode) & 0x0F)
)
);
int dLength = ExtensionData.Length + ApplicationData.Length;
int pLength = 0;
if (dLength < 126)
{
pLength = (dLength) | (Mask ? 0x80 : 0x00);
stream.WriteByte((byte)pLength);
} else if (dLength < (1U<<16))
{
pLength = (126) | (Mask ? 0x80 : 0x00);
stream.WriteByte((byte)pLength);
stream.WriteBytes(((ushort)dLength).GetBytes(true));
}
else
{
pLength = (127) | (Mask ? 0x80 : 0x00);
stream.WriteByte((byte)pLength);
stream.WriteBytes(((ulong)dLength).GetBytes(true));
}
byte[] payload = Payload;
if (Mask)
{
stream.WriteBytes(MaskingKey.GetBytes());
MaskPayload(payload, MaskingKey);
}
stream.WriteBytes(payload);
stream.Flush();
}
public static void MaskPayload(byte[] data,int maskingKey)
{
byte[] mk = BitConverter.GetBytes(maskingKey);
for (int n = 0; n < data.Length; n++)
{
data[n] = (byte)(data[n] ^ mk[n % 4]);
}
}
static Random random = new Random();
}
}