diff --git a/MTSC-TestClient/Program.cs b/MTSC-TestClient/Program.cs
index 4c67b00..2fd4eb8 100644
--- a/MTSC-TestClient/Program.cs
+++ b/MTSC-TestClient/Program.cs
@@ -9,7 +9,7 @@ namespace MTSC_TestClient
{
static void Main(string[] args)
{
- Client client = new Client();
+ Client client = new Client(true);
BroadcastHandler broadcastHandler = new BroadcastHandler(client);
client
.SetServerAddress("127.0.0.1")
diff --git a/MTSC-TestServer/Program.cs b/MTSC-TestServer/Program.cs
index 250c563..e473888 100644
--- a/MTSC-TestServer/Program.cs
+++ b/MTSC-TestServer/Program.cs
@@ -3,6 +3,7 @@ using MTSC.Logging;
using MTSC.Server;
using MTSC.Server.Handlers;
using System;
+using System.Numerics;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
@@ -16,14 +17,15 @@ namespace MTSC_TestServer
Server server = new Server(555);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024);
EncryptionHandler encryptionHandler = new EncryptionHandler(rsa, server);
- BroadcastHandler broadcastHandler = new BroadcastHandler(server);
server
//.AddHandler(encryptionHandler)
.AddLogger(new ConsoleLogger())
.AddLogger(new DebugConsoleLogger())
.AddExceptionHandler(new ExceptionConsoleLogger())
- .AddHandler(broadcastHandler)
+ .AddHandler(new BroadcastHandler(server))
+ .AddHandler(new HttpHandler(server))
.Run();
}
+
}
}
diff --git a/MTSC/Client/Handlers/HttpHandler.cs b/MTSC/Client/Handlers/HttpHandler.cs
new file mode 100644
index 0000000..8da3f34
--- /dev/null
+++ b/MTSC/Client/Handlers/HttpHandler.cs
@@ -0,0 +1,112 @@
+using MTSC.Common.Http;
+using MTSC.Common.Http.ClientModules;
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Text;
+
+namespace MTSC.Client.Handlers
+{
+ ///
+ /// Handler for handling client http communication.
+ ///
+ public class HttpHandler : IHandler
+ {
+ #region Fields
+ Client managedClient;
+ List httpModules = new List();
+ #endregion
+ #region Constructors
+ public HttpHandler(Client client)
+ {
+ managedClient = client;
+ }
+ #endregion
+ #region Public Methods
+ ///
+ /// Send a request to the server.
+ ///
+ /// Request to be sent.
+ public void SendRequest(HttpMessage request)
+ {
+ managedClient.QueueMessage(request.GetRequest());
+ }
+ ///
+ /// Add a http module.
+ ///
+ /// Module to be added.
+ /// This handler object.
+ public HttpHandler AddModule(IHttpModule httpModule)
+ {
+ httpModules.Add(httpModule);
+ return this;
+ }
+ #endregion
+ #region Interface Implementation
+ ///
+ /// Handler implementation
+ ///
+ ///
+ void IHandler.Disconnected(TcpClient client)
+ {
+
+ }
+ ///
+ /// Handler implementation
+ ///
+ ///
+ ///
+ /// False.
+ bool IHandler.HandleReceivedMessage(TcpClient client, Message message)
+ {
+ HttpMessage httpMessage = new HttpMessage();
+ httpMessage.ParseResponse(message.MessageBytes);
+ foreach(IHttpModule httpModule in httpModules)
+ {
+ if (httpModule.HandleResponse(this, httpMessage))
+ {
+ break;
+ }
+ }
+ return false;
+ }
+ ///
+ /// Handler implementation
+ ///
+ ///
+ ///
+ /// False.
+ bool IHandler.HandleSendMessage(TcpClient client, ref Message message)
+ {
+ return false;
+ }
+ ///
+ /// Handler implementation.
+ ///
+ ///
+ /// True.
+ bool IHandler.InitializeConnection(TcpClient client)
+ {
+ return true;
+ }
+ ///
+ /// Handler imeplementation.
+ ///
+ ///
+ ///
+ /// False.
+ bool IHandler.PreHandleReceivedMessage(TcpClient client, ref Message message)
+ {
+ return false;
+ }
+ ///
+ /// Handler implementation.
+ ///
+ ///
+ void IHandler.Tick(TcpClient tcpClient)
+ {
+
+ }
+ #endregion
+ }
+}
diff --git a/MTSC/Common/HTTPMessage.cs b/MTSC/Common/HTTPMessage.cs
deleted file mode 100644
index c994cdd..0000000
--- a/MTSC/Common/HTTPMessage.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace MTSC.Common
-{
- public class HTTPMessage
- {
- public enum StatusCodes
- {
- Continue = 100,
- SwitchingProtocols = 101,
- OK = 200,
- Created = 201,
- Accepted = 202,
- NonAuthoritativeInformation = 203,
- NoContent = 204,
- ResetContent = 205,
- PartialContent = 206,
- MultipleChoices = 300,
- MovedPermanently = 301,
- Found = 302,
- SeeOther = 303,
- NotModified = 304,
- UseProxy = 305,
- TemporaryRedirect = 307,
- BadRequest = 400,
- Unauthorized = 401,
- PaymentRequired = 402,
- Forbidden = 403,
- NotFound = 404,
- MethodNotAllowed = 405,
- NotAcceptable = 406,
- ProxyAuthenticationRequired = 407,
- RequestTimeout = 408,
- Conflict = 409,
- Gone = 410,
- LengthRequired = 411,
- PreconditionFailed = 412,
- RequestEntityTooLarge = 413,
- RequestURITooLarge = 414,
- UnsupportedMediaType = 415,
- RequestRangeNotSatisfiable = 416,
- ExpectationFailed = 417,
- InternalServerError = 500,
- NotImplemented = 501,
- BadGateway = 502,
- ServiceUnavailable = 503,
- GatewayTimeout = 504,
- HTTPVersionNotSupported = 505
- }
- public enum MethodEnum
- {
- Options = 0,
- Get = 1,
- Head = 2,
- Post = 3,
- Put = 4,
- Delete = 5,
- Trace = 6,
- Connect = 7,
- ExtensionMethod = 8
- }
- public enum GeneralHeadersEnum
- {
- CacheControl = 0,
- Connection = 1,
- Date = 2,
- Pragma = 3,
- Trailer = 4,
- TransferEncoding = 5,
- Upgrade = 6,
- Via = 7,
- Warning = 8
- }
- public enum RequestHeadersEnum
- {
- Accept = 0,
- AcceptCharset = 1,
- AcceptEncoding = 2,
- AcceptLanguage = 3,
- Authorization = 4,
- Expect = 5,
- From = 6,
- Host = 7,
- IfMatch = 8,
- IfModifiedSince = 9,
- IfNoneMatch = 10,
- IfRange = 11,
- IfUnmodifiedSince = 12,
- MaxForwards = 13,
- ProxyAuthorization = 14,
- Range = 15,
- Referer = 16,
- TE = 17,
- UserAgent = 18
- }
- public enum ResponseHeadersEnum
- {
- AcceptRanges = 0,
- Age = 1,
- ETag = 2,
- Location = 3,
- ProxyAuthentication = 4,
- RetryAfter = 5,
- Server = 6,
- Vary = 7,
- WWWAuthenticate = 8
- }
- public enum EntityHeadersEnum
- {
- Allow = 0,
- ContentEncoding = 1,
- ContentLanguage = 2,
- ContentLength = 3,
- ContentLocation = 4,
- ContentMD5 = 5,
- ContentRange = 6,
- ContentType = 7,
- Expired = 8,
- LastModified = 9
- }
- private static string[] methods = new string[] { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", "extension-method" };
- private static string[] generalHeaders = new string[] { "Cache-Control", "Connection", "Date", "Pragma", "Trailer", "Transfer-Encoding", "Upgrade", "Via", "Warning" };
- private static string[] requestHeaders = new string[] { "Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language", "Authorization", "Expect", "From", "Host", "If-Match",
- "If-Modified-Since", "If-None-Match", "If-Range", "If-Unmodified-Since", "Max-Forwards", "Proxy-Authorizatio", "Range", "Referer", "TE", "User-Agent"};
- private static string[] responseHeaders = new string[] { "Accept-Ranges", "Age", "ETag", "Location", "Retry-After", "Server", "Vary", "WWW-Authenticate" };
- private static string[] entityHeaders = new string[] { "Allow", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type",
- "Expires", "Last-Modified" };
-
- private static char SP = ' ';
- private static char HT = '\t';
- private static string CRLF = "\r\n";
- private static string HTTPVER = "HTTP/1.1";
-
- /*
- * A request must follow the first line with: Method [SPACE] Request-URI [SPACE] HTTP-Ver [CR][LF]
- * Request URI = "*" | absoulteURI | absPath | auth
- * "*" - means the request applies to the server, not to a particular resource.
- * Authority is used by CONNECT method.
- */
-
- /*
- * Resume page 50
- */
-
- private Dictionary headers = new Dictionary();
-
- public MethodEnum Method { get; set; }
- public Uri RequestURI { get; set; }
- public byte[] Body { get; set; }
-
- public HTTPMessage()
- {
-
- }
-
- public void AddGeneralHeader(GeneralHeadersEnum header, string value)
- {
- headers.Add(generalHeaders[(int)header], value);
- }
-
- public void AddRequestHeader(RequestHeadersEnum requestHeader, string value)
- {
- headers.Add(requestHeaders[(int)requestHeader], value);
- }
-
- public void AddResponseHeader(ResponseHeadersEnum responseHeader, string value)
- {
- headers.Add(responseHeaders[(int)responseHeader], value);
- }
-
- public void AddEntityHeaders(EntityHeadersEnum entityHeader, string value)
- {
- headers.Add(entityHeaders[(int)entityHeader], value);
- }
-
- public string GetRequest()
- {
- StringBuilder request = new StringBuilder();
- request.Append(Method.ToString()).Append(SP).Append(RequestURI.ToString()).Append(HTTPVER).Append(CRLF);
- foreach(KeyValuePair header in headers)
- {
- request.Append(header.Key).Append(':').Append(SP).Append(header.Value).Append(CRLF);
- }
- request.Append(CRLF);
- return request.ToString();
- }
- }
-}
diff --git a/MTSC/Common/Http/ClientModules/IHttpModule.cs b/MTSC/Common/Http/ClientModules/IHttpModule.cs
new file mode 100644
index 0000000..7118718
--- /dev/null
+++ b/MTSC/Common/Http/ClientModules/IHttpModule.cs
@@ -0,0 +1,20 @@
+using MTSC.Client.Handlers;
+using MTSC.Common.Http;
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Text;
+
+namespace MTSC.Common.Http.ClientModules
+{
+ public interface IHttpModule
+ {
+ ///
+ /// Handle a response received from the server.
+ ///
+ /// Handler that operates on the response.
+ /// Response message.
+ /// True if no other module should operate on the response.
+ bool HandleResponse(IHandler handler, HttpMessage response);
+ }
+}
diff --git a/MTSC/Common/Http/HttpMessage.cs b/MTSC/Common/Http/HttpMessage.cs
new file mode 100644
index 0000000..eaf7968
--- /dev/null
+++ b/MTSC/Common/Http/HttpMessage.cs
@@ -0,0 +1,784 @@
+using MTSC.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MTSC.Common.Http
+{
+ public class HttpMessage
+ {
+ public enum StatusCodes
+ {
+ Continue = 100,
+ SwitchingProtocols = 101,
+ OK = 200,
+ Created = 201,
+ Accepted = 202,
+ NonAuthoritativeInformation = 203,
+ NoContent = 204,
+ ResetContent = 205,
+ PartialContent = 206,
+ MultipleChoices = 300,
+ MovedPermanently = 301,
+ Found = 302,
+ SeeOther = 303,
+ NotModified = 304,
+ UseProxy = 305,
+ TemporaryRedirect = 307,
+ BadRequest = 400,
+ Unauthorized = 401,
+ PaymentRequired = 402,
+ Forbidden = 403,
+ NotFound = 404,
+ MethodNotAllowed = 405,
+ NotAcceptable = 406,
+ ProxyAuthenticationRequired = 407,
+ RequestTimeout = 408,
+ Conflict = 409,
+ Gone = 410,
+ LengthRequired = 411,
+ PreconditionFailed = 412,
+ RequestEntityTooLarge = 413,
+ RequestURITooLarge = 414,
+ UnsupportedMediaType = 415,
+ RequestRangeNotSatisfiable = 416,
+ ExpectationFailed = 417,
+ InternalServerError = 500,
+ NotImplemented = 501,
+ BadGateway = 502,
+ ServiceUnavailable = 503,
+ GatewayTimeout = 504,
+ HTTPVersionNotSupported = 505
+ }
+ public enum MethodEnum
+ {
+ Options = 0,
+ Get = 1,
+ Head = 2,
+ Post = 3,
+ Put = 4,
+ Delete = 5,
+ Trace = 6,
+ Connect = 7,
+ ExtensionMethod = 8
+ }
+ public enum GeneralHeadersEnum
+ {
+ CacheControl = 0,
+ Connection = 1,
+ Date = 2,
+ Pragma = 3,
+ Trailer = 4,
+ TransferEncoding = 5,
+ Upgrade = 6,
+ Via = 7,
+ Warning = 8
+ }
+ public enum RequestHeadersEnum
+ {
+ Accept = 0,
+ AcceptCharset = 1,
+ AcceptEncoding = 2,
+ AcceptLanguage = 3,
+ Authorization = 4,
+ Expect = 5,
+ From = 6,
+ Host = 7,
+ IfMatch = 8,
+ IfModifiedSince = 9,
+ IfNoneMatch = 10,
+ IfRange = 11,
+ IfUnmodifiedSince = 12,
+ MaxForwards = 13,
+ ProxyAuthorization = 14,
+ Range = 15,
+ Referer = 16,
+ TE = 17,
+ UserAgent = 18
+ }
+ public enum ResponseHeadersEnum
+ {
+ AcceptRanges = 0,
+ Age = 1,
+ ETag = 2,
+ Location = 3,
+ ProxyAuthentication = 4,
+ RetryAfter = 5,
+ Server = 6,
+ Vary = 7,
+ WWWAuthenticate = 8
+ }
+ public enum EntityHeadersEnum
+ {
+ Allow = 0,
+ ContentEncoding = 1,
+ ContentLanguage = 2,
+ ContentLength = 3,
+ ContentLocation = 4,
+ ContentMD5 = 5,
+ ContentRange = 6,
+ ContentType = 7,
+ Expired = 8,
+ LastModified = 9
+ }
+ private static string[] methods = new string[] { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", "extension-method" };
+ private static string[] generalHeaders = new string[] { "Cache-Control", "Connection", "Date", "Pragma", "Trailer", "Transfer-Encoding", "Upgrade", "Via", "Warning" };
+ private static string[] requestHeaders = new string[] { "Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language", "Authorization", "Expect", "From", "Host", "If-Match",
+ "If-Modified-Since", "If-None-Match", "If-Range", "If-Unmodified-Since", "Max-Forwards", "Proxy-Authorizatio", "Range", "Referer", "TE", "User-Agent"};
+ private static string[] responseHeaders = new string[] { "Accept-Ranges", "Age", "ETag", "Location", "Retry-After", "Server", "Vary", "WWW-Authenticate" };
+ private static string[] entityHeaders = new string[] { "Allow", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type",
+ "Expires", "Last-Modified" };
+
+ private static char SP = ' ';
+ private static char HT = '\t';
+ private static string CRLF = "\r\n";
+ private static string HTTPVER = "HTTP/1.1";
+
+ private Dictionary headers = new Dictionary();
+
+ #region Properties
+ public MethodEnum Method { get; set; }
+ public string RequestURI { get; set; }
+ public byte[] Body { get; set; }
+ public StatusCodes StatusCode { get; set; }
+ #endregion
+ #region Constructors
+ public HttpMessage()
+ {
+
+ }
+ #endregion
+ #region Public Methods
+ ///
+ /// Headers dictionary.
+ ///
+ /// Key of the header.
+ /// Value of the header.
+ public string this[string headerKey] { get => headers[headerKey]; set => headers[headerKey] = value; }
+ ///
+ /// Headers dictionary.
+ ///
+ /// Key of the header.
+ /// Value of the header.
+ public string this[GeneralHeadersEnum headerKey] { get => headers[generalHeaders[(int)headerKey]]; set => headers[generalHeaders[(int)headerKey]] = value; }
+ ///
+ /// Headers dictionary.
+ ///
+ /// Key of the header.
+ /// Value of the header.
+ public string this[ResponseHeadersEnum headerKey] { get => headers[responseHeaders[(int)headerKey]]; set => headers[responseHeaders[(int)headerKey]] = value; }
+ ///
+ /// Headers dictionary.
+ ///
+ /// Key of the header.
+ /// Value of the header.
+ public string this[RequestHeadersEnum headerKey] { get => headers[requestHeaders[(int)headerKey]]; set => headers[requestHeaders[(int)headerKey]] = value; }
+ ///
+ /// Add a general header to the message.
+ ///
+ /// Header key.
+ /// Header value.
+ public void AddGeneralHeader(GeneralHeadersEnum header, string value)
+ {
+ headers.Add(generalHeaders[(int)header], value);
+ }
+ ///
+ /// Add a request header to the message.
+ ///
+ /// Header key.
+ /// Header value.
+ public void AddRequestHeader(RequestHeadersEnum requestHeader, string value)
+ {
+ headers.Add(requestHeaders[(int)requestHeader], value);
+ }
+ ///
+ /// Add a response header to the message.
+ ///
+ /// Header key.
+ /// Header value.
+ public void AddResponseHeader(ResponseHeadersEnum responseHeader, string value)
+ {
+ headers.Add(responseHeaders[(int)responseHeader], value);
+ }
+ ///
+ /// Add an entity header to the message.
+ ///
+ /// Header key.
+ /// Header value.
+ public void AddEntityHeaders(EntityHeadersEnum entityHeader, string value)
+ {
+ headers.Add(entityHeaders[(int)entityHeader], value);
+ }
+ ///
+ /// Build the request bytes based on the message contents.
+ ///
+ /// Array of bytes.
+ public byte[] GetRequest()
+ {
+ StringBuilder requestString = new StringBuilder();
+ requestString.Append(Method.ToString()).Append(SP).Append(RequestURI.ToString()).Append(SP).Append(HTTPVER).Append(CRLF);
+ foreach(KeyValuePair header in headers)
+ {
+ requestString.Append(header.Key).Append(':').Append(SP).Append(header.Value).Append(CRLF);
+ }
+ requestString.Append(CRLF);
+ byte[] request = new byte[requestString.Length + (Body == null ? 0 : Body.Length)];
+ byte[] requestBytes = ASCIIEncoding.ASCII.GetBytes(requestString.ToString());
+ Array.Copy(requestBytes, 0, request, 0, requestBytes.Length);
+ if (Body != null)
+ {
+ Array.Copy(Body, 0, request, requestBytes.Length, Body.Length);
+ }
+ return request;
+ }
+ ///
+ /// Parse the received bytes and populate the message contents.
+ ///
+ /// Message bytes to be parsed.
+ public void ParseRequest(byte[] requestBytes)
+ {
+ /*
+ * Parse the bytes one by one, respecting the reference manual.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ /*
+ * Keep the index of the byte array, to identify the message body.
+ * Step value indicates at what point the parsing algorithm currently is.
+ * Step 0 - Method, 1 - URI, 2 - HTTPVer, 3 - Header, 4 - Value
+ */
+ int step = 0;
+ string headerKey = string.Empty;
+ string headerValue = string.Empty;
+ int bodyIndex = 0;
+ for(int i = 0; i < requestBytes.Length; i++)
+ {
+ if(step == 0)
+ {
+ Method = ParseMethod(requestBytes, ref i);
+ step++;
+ }
+ else if(step == 1)
+ {
+ RequestURI = ParseRequestURI(requestBytes, ref i);
+ step++;
+ }
+ else if (step == 2)
+ {
+ ParseHTTPVer(requestBytes, ref i);
+ step++;
+ }
+ else if (step == 3)
+ {
+ if(requestBytes[i] == CRLF[0])
+ {
+ continue;
+ }
+ else if(requestBytes[i] == CRLF[1])
+ {
+ bodyIndex = i;
+ break;
+ }
+ else
+ {
+ headerKey = ParseHeaderKey(requestBytes, ref i);
+ step++;
+ }
+ }
+ else if (step == 4)
+ {
+ if (requestBytes[i] == CRLF[0])
+ {
+ continue;
+ }
+ else if (requestBytes[i] == CRLF[1])
+ {
+ bodyIndex = i;
+ break;
+ }
+ else
+ {
+ headerValue = ParseHeaderValue(requestBytes, ref i);
+ headers.Add(headerKey, headerValue);
+ step--;
+ }
+ }
+ }
+ if(requestBytes.Length - bodyIndex > 1)
+ {
+ /*
+ * If the message contains a body, copy it into a different array
+ * and save it into the HTTP message;
+ */
+ this.Body = new byte[requestBytes.Length - bodyIndex];
+ Array.Copy(requestBytes, bodyIndex, this.Body, 0, this.Body.Length);
+ }
+ return;
+ }
+ ///
+ /// Build the response bytes based on the message contents.
+ ///
+ /// Array of bytes.
+ public byte[] GetResponse()
+ {
+ StringBuilder responseString = new StringBuilder();
+ responseString.Append(HTTPVER).Append(SP).Append((int)this.StatusCode).Append(SP).Append(this.StatusCode.ToString()).Append(CRLF);
+ responseString.Append(CRLF);
+ byte[] response = new byte[responseString.Length + (Body == null ? 0 : Body.Length)];
+ byte[] responseBytes = ASCIIEncoding.ASCII.GetBytes(responseString.ToString());
+ Array.Copy(responseBytes, 0, response, 0, responseBytes.Length);
+ if (Body != null)
+ {
+ Array.Copy(Body, 0, response, responseBytes.Length, Body.Length);
+ }
+ return response;
+ }
+ ///
+ /// Parse the received bytes and populate the message contents.
+ ///
+ /// Array of bytes to be parsed.
+ public void ParseResponse(byte[] responseBytes)
+ {
+ /*
+ * Parse the bytes one by one, respecting the reference manual.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ /*
+ * Keep the index of the byte array, to identify the message body.
+ * Step value indicates at what point the parsing algorithm currently is.
+ * Step 0 - HTTPVer, 1 - StatusCodeInt, 2 - StatusCodeString, 3 - Header, 4 - Value
+ */
+ int step = 0;
+ string headerKey = string.Empty;
+ string headerValue = string.Empty;
+ int bodyIndex = 0;
+ for (int i = 0; i < responseBytes.Length; i++)
+ {
+ if (step == 0)
+ {
+ ParseHTTPVer(responseBytes, ref i);
+ step++;
+ }
+ else if (step == 1)
+ {
+ StatusCode = (StatusCodes)ParseResponseCode(responseBytes, ref i);
+ step++;
+ }
+ else if (step == 2)
+ {
+ if(StatusCode != ParseResponseCodeString(responseBytes, ref i))
+ {
+ throw new InvalidStatusCodeException("Status code value and text do not match!");
+ }
+ step++;
+ }
+ else if (step == 3)
+ {
+ if (responseBytes[i] == CRLF[0])
+ {
+ continue;
+ }
+ else if (responseBytes[i] == CRLF[1])
+ {
+ bodyIndex = i;
+ break;
+ }
+ else
+ {
+ headerKey = ParseHeaderKey(responseBytes, ref i);
+ step++;
+ }
+ }
+ else if (step == 4)
+ {
+ if (responseBytes[i] == CRLF[0])
+ {
+ continue;
+ }
+ else if (responseBytes[i] == CRLF[1])
+ {
+ bodyIndex = i;
+ break;
+ }
+ else
+ {
+ headerValue = ParseHeaderValue(responseBytes, ref i);
+ headers.Add(headerKey, headerValue);
+ step--;
+ }
+ }
+ }
+ if (responseBytes.Length - bodyIndex > 1)
+ {
+ /*
+ * If the message contains a body, copy it into a different array
+ * and save it into the HTTP message;
+ */
+ this.Body = new byte[responseBytes.Length - bodyIndex - 1];
+ Array.Copy(responseBytes, bodyIndex, this.Body, 0, this.Body.Length);
+ }
+ return;
+ }
+ ///
+ /// Check if the message contains a header.
+ ///
+ /// Key of the header.
+ /// True if the message contains a header with the provided key.
+ public bool ContainsHeader(ResponseHeadersEnum header)
+ {
+ return ContainsHeader(responseHeaders[(int)header]);
+ }
+ ///
+ /// Check if the message contains a header.
+ ///
+ /// Key of the header.
+ /// True if the message contains a header with the provided key.
+ public bool ContainsHeader(RequestHeadersEnum header)
+ {
+ return ContainsHeader(requestHeaders[(int)header]);
+ }
+ ///
+ /// Check if the message contains a header.
+ ///
+ /// Key of the header.
+ /// True if the message contains a header with the provided key.
+ public bool ContainsHeader(GeneralHeadersEnum header)
+ {
+ return ContainsHeader(generalHeaders[(int)header]);
+ }
+ ///
+ /// Check if the message contains a header.
+ ///
+ /// Key of the header.
+ /// True if the message contains a header with the provided key.
+ public bool ContainsHeader(string header)
+ {
+ return headers.ContainsKey(header);
+ }
+ ///
+ /// Parse the body into a posted from respecting the reference manual.
+ ///
+ /// Dictionary with posted from.
+ public Dictionary GetPostForm()
+ {
+ if (ContainsHeader("Content-Type") && Body != null)
+ {
+ if(this["Content-Type"] == "application/x-www-form-urlencoded")
+ {
+ Dictionary returnDictionary = new Dictionary();
+ /*
+ * Walk through the buffer and get the form contents.
+ * Step 0 - key, 1 - value.
+ */
+ string formKey = string.Empty;
+ int step = 0;
+ for(int i = 0; i < Body.Length; i++)
+ {
+ if(step == 0)
+ {
+ formKey = GetField(Body, ref i);
+ step++;
+ }
+ else
+ {
+ returnDictionary[formKey] = GetValue(Body, ref i);
+ step--;
+ }
+ }
+ return returnDictionary;
+ }
+ else if (this["Content-Type"].Contains("multipart/form-data"))
+ {
+ throw new NotImplementedException("Multipart posting not implemented");
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+ #endregion
+ #region Private Methods
+ private MethodEnum GetMethod(string methodString)
+ {
+ int index = Array.IndexOf(methods, methodString);
+ return (MethodEnum)index;
+ }
+
+ private MethodEnum ParseMethod(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a SP character, parse the method, clear the buffer
+ * and continue with parsing the next step.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == (byte)SP)
+ {
+ string methodString = parseBuffer.ToString();
+ return GetMethod(methodString);
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new MethodInvalidException("Invalid request method. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new MethodInvalidException("Invalid request method. Buffer: " + parseBuffer.ToString());
+ }
+
+ private string ParseRequestURI(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == (byte)SP)
+ {
+ return parseBuffer.ToString();
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidRequestURIException("Invalid request URI. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new InvalidRequestURIException("Invalid request URI. Buffer: " + parseBuffer.ToString());
+ }
+
+ private void ParseHTTPVer(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a LF character, parse the HTTPVer.
+ * Check if the HTTPVer matches the implementation version.
+ * If not, throw an exception.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == CRLF[1] || buffer[index] == SP)
+ {
+ string httpVer = parseBuffer.ToString();
+ if (httpVer != HTTPVER)
+ {
+ throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString());
+ }
+ return;
+ }
+ else if(buffer[index] == CRLF[0])
+ {
+ /*
+ * If a termination character is detected, ignore it and wait for the full terminator.
+ */
+ continue;
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ }
+
+ private string ParseHeaderKey(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a ':' character, parse the header key.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == ':')
+ {
+ return parseBuffer.ToString();
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString());
+ }
+
+ private string ParseHeaderValue(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a LF character, parse the value.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == CRLF[1])
+ {
+ return parseBuffer.ToString().Trim();
+ }
+ else if (buffer[index] == CRLF[0])
+ {
+ /*
+ * If a termination character is detected, ignore it and wait for the full terminator.
+ */
+ continue;
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString());
+ }
+
+ private int ParseResponseCode(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a SP character, parse the value.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == SP)
+ {
+ return int.Parse(parseBuffer.ToString());
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString());
+ }
+
+ private StatusCodes ParseResponseCodeString(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a LF character, parse the value.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == CRLF[1])
+ {
+ return (StatusCodes)Enum.Parse(typeof(StatusCodes), parseBuffer.ToString().Trim());
+ }
+ else if (buffer[index] == CRLF[0])
+ {
+ /*
+ * If a termination character is detected, ignore it and wait for the full terminator.
+ */
+ continue;
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new InvalidHeaderException("Invalid status code. Buffer: " + parseBuffer.ToString());
+ }
+
+ private string GetField(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a LF character, parse the value.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == '=')
+ {
+ return parseBuffer.ToString().Trim();
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidPostFormException("Invalid form field. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ throw new InvalidHeaderException("Invalid form field. Buffer: " + parseBuffer.ToString());
+ }
+
+ private string GetValue(byte[] buffer, ref int index)
+ {
+ /*
+ * Get each character one by one. When meeting a LF character, parse the value.
+ */
+ StringBuilder parseBuffer = new StringBuilder();
+ for (; index < buffer.Length; index++)
+ {
+ try
+ {
+ if (buffer[index] == '&')
+ {
+ return parseBuffer.ToString().Trim();
+ }
+ else
+ {
+ parseBuffer.Append((char)buffer[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new InvalidPostFormException("Invalid form field. Buffer: " + parseBuffer.ToString(), e);
+ }
+ }
+ return parseBuffer.ToString().Trim();
+ }
+ #endregion
+ }
+}
diff --git a/MTSC/Common/Http/ServerModules/IHttpModule.cs b/MTSC/Common/Http/ServerModules/IHttpModule.cs
new file mode 100644
index 0000000..5b160e7
--- /dev/null
+++ b/MTSC/Common/Http/ServerModules/IHttpModule.cs
@@ -0,0 +1,24 @@
+using MTSC.Common;
+using MTSC.Server;
+using MTSC.Server.Handlers;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MTSC.Common.Http.ServerModules
+{
+ ///
+ /// Interface for Http modules used by the server http handler.
+ ///
+ public interface IHttpModule
+ {
+ ///
+ /// Handle the received request.
+ ///
+ /// Handler that handled the request.
+ /// Client data.
+ /// Request message.
+ /// True if no other module should handle the received request.
+ bool HandleRequest(IHandler handler, ClientStruct client, HttpMessage request);
+ }
+}
diff --git a/MTSC/CommunicationPrimitives.cs b/MTSC/CommunicationPrimitives.cs
index 6725f8b..7fd661a 100644
--- a/MTSC/CommunicationPrimitives.cs
+++ b/MTSC/CommunicationPrimitives.cs
@@ -25,9 +25,7 @@ namespace MTSC
{
stream = client.GetStream();
}
- byte[] controlBuffer = new byte[5];
- stream.Read(controlBuffer, 0, 4);
- uint messageLength = BitConverter.ToUInt32(controlBuffer, 0);
+ uint messageLength = (uint)client.Available;
byte[] messageBuffer = new byte[messageLength];
if (messageLength > 0)
{
diff --git a/MTSC/Exceptions/InvalidHeaderException.cs b/MTSC/Exceptions/InvalidHeaderException.cs
new file mode 100644
index 0000000..29f0a43
--- /dev/null
+++ b/MTSC/Exceptions/InvalidHeaderException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Exception for invalid headers.
+ ///
+ public class InvalidHeaderException : Exception
+ {
+ public InvalidHeaderException()
+ {
+ }
+
+ public InvalidHeaderException(string message) : base(message)
+ {
+ }
+
+ public InvalidHeaderException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidHeaderException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/Exceptions/InvalidHttpVersionException.cs b/MTSC/Exceptions/InvalidHttpVersionException.cs
new file mode 100644
index 0000000..892ec59
--- /dev/null
+++ b/MTSC/Exceptions/InvalidHttpVersionException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Exception in case of Invalid HTTP Version.
+ ///
+ public class InvalidHttpVersionException : Exception
+ {
+ public InvalidHttpVersionException()
+ {
+ }
+
+ public InvalidHttpVersionException(string message) : base(message)
+ {
+ }
+
+ public InvalidHttpVersionException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidHttpVersionException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/Exceptions/InvalidPostFormException.cs b/MTSC/Exceptions/InvalidPostFormException.cs
new file mode 100644
index 0000000..64f5905
--- /dev/null
+++ b/MTSC/Exceptions/InvalidPostFormException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Exception for invalid post forms.
+ ///
+ public class InvalidPostFormException : Exception
+ {
+ public InvalidPostFormException()
+ {
+ }
+
+ public InvalidPostFormException(string message) : base(message)
+ {
+ }
+
+ public InvalidPostFormException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidPostFormException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/Exceptions/InvalidRequestURIException.cs b/MTSC/Exceptions/InvalidRequestURIException.cs
new file mode 100644
index 0000000..cb7468d
--- /dev/null
+++ b/MTSC/Exceptions/InvalidRequestURIException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Exception cause by an invalid request URI.
+ ///
+ public class InvalidRequestURIException : Exception
+ {
+ public InvalidRequestURIException()
+ {
+ }
+
+ public InvalidRequestURIException(string message) : base(message)
+ {
+ }
+
+ public InvalidRequestURIException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidRequestURIException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/Exceptions/InvalidStatusCodeException.cs b/MTSC/Exceptions/InvalidStatusCodeException.cs
new file mode 100644
index 0000000..9438e73
--- /dev/null
+++ b/MTSC/Exceptions/InvalidStatusCodeException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Exception for invalid response status code.
+ ///
+ public class InvalidStatusCodeException : Exception
+ {
+ public InvalidStatusCodeException()
+ {
+ }
+
+ public InvalidStatusCodeException(string message) : base(message)
+ {
+ }
+
+ public InvalidStatusCodeException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected InvalidStatusCodeException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/Exceptions/MethodInvalidException.cs b/MTSC/Exceptions/MethodInvalidException.cs
new file mode 100644
index 0000000..c974375
--- /dev/null
+++ b/MTSC/Exceptions/MethodInvalidException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace MTSC.Exceptions
+{
+ ///
+ /// Invalid HTTP Method Exception.
+ ///
+ public class MethodInvalidException : Exception
+ {
+ public MethodInvalidException()
+ {
+ }
+
+ public MethodInvalidException(string message) : base(message)
+ {
+ }
+
+ public MethodInvalidException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected MethodInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/MTSC/Server/Handlers/HttpHandler.cs b/MTSC/Server/Handlers/HttpHandler.cs
new file mode 100644
index 0000000..235ed81
--- /dev/null
+++ b/MTSC/Server/Handlers/HttpHandler.cs
@@ -0,0 +1,112 @@
+using MTSC.Common;
+using MTSC.Common.Http;
+using MTSC.Common.Http.ServerModules;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MTSC.Server.Handlers
+{
+ ///
+ /// Handler for handling server http requests.
+ ///
+ public class HttpHandler : IHandler
+ {
+ #region Fields
+ Server managedServer;
+ List httpModules = new List();
+ #endregion
+ #region Constructors
+ public HttpHandler(Server server)
+ {
+ this.managedServer = server;
+ }
+ #endregion
+ #region Public Methods
+ ///
+ /// Add a http module onto the server.
+ ///
+ /// Module to be added.
+ /// This handler object.
+ public HttpHandler AddHttpModule(IHttpModule module)
+ {
+ this.httpModules.Add(module);
+ return this;
+ }
+ ///
+ /// Send a response back to the client.
+ ///
+ /// Message containing the response.
+ public void SendResponse(ClientStruct client, HttpMessage response)
+ {
+ byte[] responseBytes = response.GetResponse();
+ managedServer.QueueMessage(client, responseBytes);
+ }
+ #endregion
+ #region Interface Implementation
+ ///
+ /// Handler interface implementation.
+ ///
+ ///
+ void IHandler.ClientRemoved(ClientStruct client)
+ {
+
+ }
+ ///
+ /// Handler interface implementation.
+ ///
+ ///
+ ///
+ bool IHandler.HandleClient(ClientStruct client)
+ {
+ return false;
+ }
+ ///
+ /// Handler interface implementation.
+ ///
+ ///
+ ///
+ ///
+ bool IHandler.HandleReceivedMessage(ClientStruct client, Message message)
+ {
+ HttpMessage httpMessage = new HttpMessage();
+ httpMessage.ParseRequest(message.MessageBytes);
+ foreach(IHttpModule module in httpModules)
+ {
+ if(module.HandleRequest(this, client, httpMessage))
+ {
+ break;
+ }
+ }
+ return false;
+ }
+ ///
+ /// Handler interface implementation.
+ ///
+ ///
+ ///
+ ///
+ bool IHandler.HandleSendMessage(ClientStruct client, ref Message message)
+ {
+ return false;
+ }
+ ///
+ /// Handler interface implementation.
+ ///
+ ///
+ ///
+ ///
+ bool IHandler.PreHandleReceivedMessage(ClientStruct client, ref Message message)
+ {
+ return false;
+ }
+ ///
+ /// Handler interface implementation.
+ ///
+ void IHandler.Tick()
+ {
+
+ }
+ #endregion
+ }
+}
diff --git a/MTSC/Server/Server.cs b/MTSC/Server/Server.cs
index 2c23fca..e0146b6 100644
--- a/MTSC/Server/Server.cs
+++ b/MTSC/Server/Server.cs
@@ -173,26 +173,43 @@ namespace MTSC.Server
/*
* Check if the server has any pending connections.
* If it has a new connection, process it.
- */
- if (listener.Pending())
+ */
+ try
{
- TcpClient tcpClient = listener.AcceptTcpClient();
- ClientStruct clientStruct = new ClientStruct(tcpClient);
- if(certificate != null)
+ if (listener.Pending())
{
- SslStream sslStream = new SslStream(tcpClient.GetStream());
- clientStruct.SslStream = sslStream;
- sslStream.AuthenticateAsServer(certificate);
+ TcpClient tcpClient = listener.AcceptTcpClient();
+ ClientStruct clientStruct = new ClientStruct(tcpClient);
+ if (certificate != null)
+ {
+ SslStream sslStream = new SslStream(tcpClient.GetStream(), true, new RemoteCertificateValidationCallback((o, c, ch, po) => {
+ return true;
+ }), null, EncryptionPolicy.RequireEncryption);
+ clientStruct.SslStream = sslStream;
+ sslStream.AuthenticateAsServer(certificate);
+ }
+ foreach (IHandler handler in handlers)
+ {
+ if (handler.HandleClient(clientStruct))
+ {
+ break;
+ }
+ }
+ clients.Add(clientStruct);
+ Log("Accepted new connection: " + tcpClient.Client.RemoteEndPoint.ToString());
}
- foreach(IHandler handler in handlers)
+ }
+ catch(Exception e)
+ {
+ LogDebug("Exception: " + e.Message);
+ LogDebug("Stacktrace: " + e.StackTrace);
+ foreach (IExceptionHandler exceptionHandler in exceptionHandlers)
{
- if (handler.HandleClient(clientStruct))
+ if (exceptionHandler.HandleException(e))
{
break;
}
}
- clients.Add(clientStruct);
- Log("Accepted new connection: " + tcpClient.Client.RemoteEndPoint.ToString());
}
/*
* Process in parallel all clients.