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.