WIP
parent
f06af9d2c9
commit
92024abb13
|
@ -8,6 +8,8 @@ using ln.types.net;
|
|||
using System.Globalization;
|
||||
using ln.http.exceptions;
|
||||
using System.Threading;
|
||||
using ln.types;
|
||||
using ln.http.router;
|
||||
|
||||
|
||||
namespace ln.http
|
||||
|
@ -166,6 +168,9 @@ namespace ln.http
|
|||
{
|
||||
keepAlive = false;
|
||||
}
|
||||
|
||||
response?.ContentStream?.Dispose();
|
||||
|
||||
} while (keepAlive);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -183,7 +188,6 @@ namespace ln.http
|
|||
}
|
||||
|
||||
HttpRequest.ClearCurrent();
|
||||
|
||||
connection.GetStream().Close();
|
||||
} finally
|
||||
{
|
||||
|
@ -197,5 +201,45 @@ namespace ln.http
|
|||
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(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 argumentContainer.AdditionalArguments)
|
||||
{
|
||||
StaticRouter staticRouter = new StaticRouter(path);
|
||||
staticRouter.AddIndex("index.html");
|
||||
staticRouter.AddIndex("index.htm");
|
||||
router.AddSimpleRoute("/*", staticRouter);
|
||||
}
|
||||
|
||||
if (argumentContainer['c'].IsSet)
|
||||
router.AddSimpleRoute("/*", new RouterTarget((request) => router.Route(argumentContainer['c'].Value,request)),0);
|
||||
|
||||
HTTPServer server = new HTTPServer(new Endpoint(IPv6.Parse(argumentContainer['l'].Value),argumentContainer['p'].IntegerValue),
|
||||
new LoggingRouter(router));
|
||||
server.Start();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ using System.Text;
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using ln.types.net;
|
||||
using ln.http.message;
|
||||
using ln.http.io;
|
||||
using ln.http.message.parser;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
|
@ -20,11 +23,13 @@ namespace ln.http
|
|||
|
||||
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 Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();
|
||||
|
||||
public bool Valid { get; private set; } = false;
|
||||
|
||||
|
@ -40,19 +45,18 @@ namespace ln.http
|
|||
|
||||
public void Read()
|
||||
{
|
||||
ReadRequestHead();
|
||||
UnbufferedStreamReader reader = new UnbufferedStreamReader(Stream);
|
||||
string requestLine = reader.ReadLine();
|
||||
string[] requestTokens = requestLine.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (blen == 0)
|
||||
return;
|
||||
if (requestTokens.Length != 3)
|
||||
throw new FormatException("request line malformed");
|
||||
|
||||
Method = ReadToken();
|
||||
SkipWhiteSpace();
|
||||
URL = ReadToken();
|
||||
SkipWhiteSpace();
|
||||
Protocol = ReadToken();
|
||||
ReadLine();
|
||||
Method = requestTokens[0];
|
||||
URL = requestTokens[1];
|
||||
Protocol = requestTokens[2];
|
||||
|
||||
ReadHeaders();
|
||||
Headers = HTTP.ReadHeader(reader);
|
||||
|
||||
Valid = true;
|
||||
}
|
||||
|
@ -170,16 +174,16 @@ namespace ln.http
|
|||
return value.Trim();
|
||||
}
|
||||
|
||||
public void ReadHeaders()
|
||||
{
|
||||
while (bptr < hlen)
|
||||
{
|
||||
String name = ReadHeaderName();
|
||||
String value = ReadHeaderValue();
|
||||
//public void ReadHeaders()
|
||||
//{
|
||||
// while (bptr < hlen)
|
||||
// {
|
||||
// String name = ReadHeaderName();
|
||||
// String value = ReadHeaderValue();
|
||||
|
||||
Headers.Add(name, value);
|
||||
}
|
||||
}
|
||||
// Headers.Add(name, value);
|
||||
// }
|
||||
//}
|
||||
|
||||
public int ReadRequestBody(byte[] dst,int offset,int length)
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@ using ln.http.exceptions;
|
|||
using ln.types.net;
|
||||
using System.Threading;
|
||||
using ln.http.session;
|
||||
using ln.http.message;
|
||||
|
||||
namespace ln.http
|
||||
{
|
||||
|
@ -14,7 +15,8 @@ namespace ln.http
|
|||
static ThreadLocal<HttpRequest> current = new ThreadLocal<HttpRequest>();
|
||||
static public HttpRequest Current => current.Value;
|
||||
|
||||
Dictionary<String, String> requestHeaders;
|
||||
//Dictionary<String, String> requestHeaders;
|
||||
HeaderContainer requestHeaders;
|
||||
Dictionary<String, String> requestCookies;
|
||||
Dictionary<string, String> requestParameters;
|
||||
|
||||
|
@ -38,6 +40,7 @@ namespace ln.http
|
|||
public Session Session { get; set; }
|
||||
public HttpUser CurrentUser => Session.CurrentUser;
|
||||
|
||||
public HeaderContainer RequestHeaders => requestHeaders;
|
||||
|
||||
public MemoryStream ContentStream { get; }
|
||||
public TextReader ContentReader
|
||||
|
@ -70,7 +73,8 @@ namespace ln.http
|
|||
Protocol = httpReader.Protocol;
|
||||
RequestURL = httpReader.URL;
|
||||
|
||||
requestHeaders = new Dictionary<string, string>(httpReader.Headers);
|
||||
//requestHeaders = new Dictionary<string, string>(httpReader.Headers);
|
||||
requestHeaders = httpReader.Headers;
|
||||
requestCookies = new Dictionary<string, string>();
|
||||
requestParameters = new Dictionary<string, string>();
|
||||
|
||||
|
@ -156,7 +160,7 @@ namespace ln.http
|
|||
name = name.ToUpper();
|
||||
|
||||
if (requestHeaders.ContainsKey(name))
|
||||
return requestHeaders[name];
|
||||
return requestHeaders[name].Value;
|
||||
|
||||
return def;
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
namespace ln.http.exceptions
|
||||
{
|
||||
public class DisposeConnectionException : Exception
|
||||
{
|
||||
public DisposeConnectionException()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,13 +68,23 @@
|
|||
<Compile Include="IHTTPResource.cs" />
|
||||
<Compile Include="router\VirtualHostRouter.cs" />
|
||||
<Compile Include="IHttpRouter.cs" />
|
||||
<Compile Include="mime\MimeTypeMap.cs" />
|
||||
<Compile Include="message\MimeTypeMap.cs" />
|
||||
<Compile Include="router\RouterTarget.cs" />
|
||||
<Compile Include="router\SimpleRouter.cs" />
|
||||
<Compile Include="router\StaticRouter.cs" />
|
||||
<Compile Include="router\FileRouter.cs" />
|
||||
<Compile Include="router\LoggingRouter.cs" />
|
||||
<Compile Include="exceptions\MethodNotAllowedException.cs" />
|
||||
<Compile Include="router\WebsocketRouter.cs" />
|
||||
<Compile Include="exceptions\DisposeConnectionException.cs" />
|
||||
<Compile Include="message\Message.cs" />
|
||||
<Compile Include="message\Header.cs" />
|
||||
<Compile Include="message\HeaderContainer.cs" />
|
||||
<Compile Include="io\UnbufferedStreamreader.cs" />
|
||||
<Compile Include="message\TokenReader.cs" />
|
||||
<Compile Include="message\parser\MIME.cs" />
|
||||
<Compile Include="message\parser\HTTP.cs" />
|
||||
<Compile Include="exceptions\BadRequestException.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="exceptions\" />
|
||||
|
@ -85,7 +95,9 @@
|
|||
<Folder Include="cert\" />
|
||||
<Folder Include="listener\" />
|
||||
<Folder Include="router\" />
|
||||
<Folder Include="mime\" />
|
||||
<Folder Include="message\" />
|
||||
<Folder Include="io\" />
|
||||
<Folder Include="message\parser\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ln.logging\ln.logging.csproj">
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace ln.http.simple
|
||||
{
|
||||
class MainClass
|
||||
{
|
||||
public static void Main(string[] args) => HTTPServer.StartSimpleServer(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("ln.http.simple")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{516D661A-2DFE-4AC7-A32F-28CCF5F6A5F1}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>ln.http.simple</RootNamespace>
|
||||
<AssemblyName>ln.http.simple</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ln.http.csproj">
|
||||
<Project>{CEEEEB41-3059-46A2-A871-2ADE22C013D9}</Project>
|
||||
<Name>ln.http</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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.types;
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ln.http.mime
|
||||
namespace ln.http.message
|
||||
{
|
||||
public static class MimeTypeMap
|
||||
{
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
// *
|
||||
// **/
|
||||
using System.IO;
|
||||
using ln.http.mime;
|
||||
using ln.http.message;
|
||||
namespace ln.http.router
|
||||
{
|
||||
public class FileRouter : IHttpRouter
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
// **/
|
||||
using System;
|
||||
using System.IO;
|
||||
using ln.http.mime;
|
||||
using ln.http.message;
|
||||
using System.Collections.Generic;
|
||||
namespace ln.http.router
|
||||
{
|
||||
|
@ -53,9 +53,12 @@ namespace ln.http.router
|
|||
|
||||
if (File.Exists(finalPath))
|
||||
{
|
||||
HttpResponse httpResponse = new HttpResponse(httpRequest, new FileStream(finalPath, FileMode.Open));
|
||||
httpResponse.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(finalPath)));
|
||||
return httpResponse;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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(string path, HttpRequest httpRequest)
|
||||
{
|
||||
WebSocket websocket = CreateWebSocket(httpRequest);
|
||||
try
|
||||
{
|
||||
websocket.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Log(e);
|
||||
}
|
||||
throw new DisposeConnectionException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,6 @@ namespace ln.http.websocket
|
|||
|
||||
public delegate void WebSocketEventDelegate(WebSocket sender,WebSocketEventArgs e);
|
||||
|
||||
|
||||
public abstract class WebSocket
|
||||
{
|
||||
public HTTPServer HTTPServer => HttpRequest.HTTPServer;
|
||||
|
|
Loading…
Reference in New Issue