master
Harald Wolff 2019-08-03 12:56:33 +02:00
parent 4add84de93
commit 8b031ae438
28 changed files with 700 additions and 39924 deletions

View File

@ -58,7 +58,7 @@ namespace ln.http
}
}
public void AddApplication(Uri BaseURI,HttpApplication application)
public void AddApplication(Uri BaseURI, HttpApplication application)
{
applications[BaseURI] = application;
}
@ -114,9 +114,10 @@ namespace ln.http
AcceptConnection(tcpListener);
}
} catch (Exception e)
}
catch (Exception e)
{
Logging.Log(LogLevel.ERROR, "HTTPServer: Listener thread caught exception {0}",e);
Logging.Log(LogLevel.ERROR, "HTTPServer: Listener thread caught exception {0}", e);
Logging.Log(e);
}
@ -152,34 +153,51 @@ namespace ln.http
application.Authenticate(httpRequest);
application.Authorize(httpRequest);
response = application.GetResponse( httpRequest );
} catch (Exception e)
response = application.GetResponse(httpRequest);
}
catch (Exception e)
{
response = new HttpResponse(httpRequest,"text/plain");
response = new HttpResponse(httpRequest, "text/plain");
response.StatusCode = 500;
response.ContentWriter.WriteLine("Exception caught: {0}",e);
response.ContentWriter.WriteLine("Exception caught: {0}", e);
}
if (!response.HasCustomContentStream)
if (response == null)
{
response.ContentWriter.Flush();
MemoryStream cstream = (MemoryStream)response.ContentStream;
cstream.Position = 0;
response.SetHeader("content-length", cstream.Length.ToString());
Logging.Log(LogLevel.DEBUG, "Request {0} returned no Response", httpRequest);
}
if (SessionCache != null)
else
{
SessionCache.ApplySessionID(response,httpRequest.Session);
if (!response.HasCustomContentStream)
{
response.ContentWriter.Flush();
MemoryStream cstream = (MemoryStream)response.ContentStream;
cstream.Position = 0;
response.SetHeader("content-length", cstream.Length.ToString());
}
if (SessionCache != null)
{
SessionCache.ApplySessionID(response, httpRequest.Session);
}
response.AddCookie("LN_SEEN", DateTime.Now.ToString());
SendResponse(tcpClient.GetStream(), response);
tcpClient.Close();
}
response.AddCookie("LN_SEEN", DateTime.Now.ToString());
}
StreamWriter streamWriter = new StreamWriter(tcpClient.GetStream());
streamWriter.WriteLine("{0} {1} {2}", httpRequest.Protocol,response.StatusCode, response.StatusMessage);
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));
@ -187,19 +205,17 @@ namespace ln.http
foreach (HttpCookie httpCookie in response.Cookies)
{
streamWriter.WriteLine("Set-Cookie: {0}",httpCookie.ToString());
streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString());
}
streamWriter.WriteLine();
streamWriter.Flush();
response.ContentStream.CopyTo(tcpClient.GetStream());
response.ContentStream.CopyTo(stream);
response.ContentStream.Close();
response.ContentStream.Dispose();
tcpClient.Close();
streamWriter.Flush();
}
}
}

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.types.btree;
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();
}
}

View File

@ -43,12 +43,19 @@ namespace ln.http
}
}
public Stream GetConnectionStream()
{
return connectionStream;
}
StreamReader contentReader;
byte[] requestBody;
Stream connectionStream;
public HttpRequest(HttpReader httpReader,IPEndPoint localEndpoint)
{
connectionStream = httpReader.Stream;
LocalEndpoint = localEndpoint;
RemoteEndpoint = httpReader.RemoteEndpoint;
Method = httpReader.Method;

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.types;
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.types;
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

@ -28,6 +28,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Security" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
@ -46,10 +47,21 @@
<Compile Include="session\SessionCache.cs" />
<Compile Include="HttpUser.cs" />
<Compile Include="AuthenticationProvider.cs" />
<Compile Include="client\HttpClient.cs" />
<Compile Include="client\HttpClientRequest.cs" />
<Compile Include="client\CookieContainer.cs" />
<Compile Include="client\HttpClientResponse.cs" />
<Compile Include="HttpHeaders.cs" />
<Compile Include="HttpHeader.cs" />
<Compile Include="websocket\WebSocket.cs" />
<Compile Include="websocket\WebSocketEventArgs.cs" />
<Compile Include="websocket\WebSocketFrame.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="exceptions\" />
<Folder Include="session\" />
<Folder Include="client\" />
<Folder Include="websocket\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ln.logging\ln.logging.csproj">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,183 @@
using System;
using System.IO;
using System.Threading;
using ln.logging;
using ln.http.exceptions;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
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 class WebSocket
{
public HttpRequest HttpRequest { get; }
public Stream Stream { get; }
public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE;
public event WebSocketEventDelegate WebSocketEvent;
Thread receiverThread;
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)))
);
HTTPServer.SendResponse(Stream, httpResponse);
State = WebSocketState.OPEN;
}
public bool IsAlive => ((receiverThread != null) && receiverThread.IsAlive);
public void Start()
{
if ((receiverThread == null) || !receiverThread.IsAlive)
{
receiverThread = new Thread(() => Run());
receiverThread.Start();
}
}
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
{
//Send("Hello World!");
while (State != WebSocketState.CLOSED)
{
WebSocketFrame webSocketFrame = new WebSocketFrame(Stream);
switch (webSocketFrame.Opcode)
{
case WebSocketOpcode.TEXT:
case WebSocketOpcode.BINARY:
WebSocketEvent(this, new WebSocketEventArgs(webSocketFrame));
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 (Exception e)
{
Logging.Log(LogLevel.ERROR, "WebSocket caught Exception: {0}", e.ToString());
Logging.Log(e);
}
receiverThread = null;
}
public void Send(WebSocketFrame frame)
{
lock (Stream)
{
if (State == WebSocketState.OPEN)
frame.WriteTo(Stream);
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,175 @@
using System;
using System.IO;
using System.Text;
using ln.types;
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);
Logging.Log(LogLevel.DEBUG, "WebSocket: FirstByte=0x{0:x8}",firstByte);
int secondByte = stream.ReadByte();
if (secondByte == -1)
throw new IOException();
Logging.Log(LogLevel.DEBUG, "WebSocket: SecondByte=0x{0:x8}", secondByte);
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();
}
}