diff --git a/MTSC-TestClient/Program.cs b/MTSC-TestClient/Program.cs index 32eccf1..1c4583d 100644 --- a/MTSC-TestClient/Program.cs +++ b/MTSC-TestClient/Program.cs @@ -11,9 +11,9 @@ namespace MTSC_TestClient { static void Main(string[] args) { - Client client = new Client(); - WebsocketHandler websocketHandler = new WebsocketHandler(); - ChatModule chatModule = new ChatModule(); + var client = new Client(); + var websocketHandler = new WebsocketHandler(); + var chatModule = new ChatModule(); client .SetServerAddress("127.0.0.1") .SetPort(800) @@ -26,7 +26,7 @@ namespace MTSC_TestClient .Connect(); while (true) { - string message = Console.ReadLine(); + var message = Console.ReadLine(); chatModule.SendMessage(websocketHandler, message); } } diff --git a/MTSC-TestServer/Program.cs b/MTSC-TestServer/Program.cs index 61853ee..591ac51 100644 --- a/MTSC-TestServer/Program.cs +++ b/MTSC-TestServer/Program.cs @@ -16,7 +16,7 @@ namespace MTSC_TestServer { static void Main(string[] args) { - Server server = new Server(800); + var server = new Server(800); server .AddLogger(new ConsoleLogger()) .AddLogger(new DebugConsoleLogger()) diff --git a/MTSC.UnitTests/E2ETests.cs b/MTSC.UnitTests/E2ETests.cs index af7fb73..48ea7e7 100644 --- a/MTSC.UnitTests/E2ETests.cs +++ b/MTSC.UnitTests/E2ETests.cs @@ -10,6 +10,7 @@ using MTSC.Logging; using MTSC.ServerSide.Handlers; using MTSC.ServerSide.Schedulers; using MTSC.UnitTests.RoutingModules; +using Newtonsoft.Json; using System; using System.Diagnostics; using System.IO; @@ -55,7 +56,7 @@ namespace MTSC.UnitTests .AddRoute(HttpMessage.HttpMethods.Post, "echo") .AddRoute(HttpMessage.HttpMethods.Get, "long-running") .AddRoute(HttpMessage.HttpMethods.Post, "multipart") - .AddRoute(HttpMessage.HttpMethods.Get, "some-module") + .AddRoute(HttpMessage.HttpMethods.Post, "some-module/{someValue}/test/{intValue}/test") .WithFragmentsExpirationTime(TimeSpan.FromMilliseconds(3000)) .WithMaximumSize(250000)) .AddLogger(new ConsoleLogger()) @@ -69,7 +70,7 @@ namespace MTSC.UnitTests [TestMethod] public async Task ServerReturns500OnError() { - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:800"); var response = await httpClient.GetAsync("throw"); Assert.AreEqual(response.StatusCode, HttpStatusCode.InternalServerError); @@ -78,26 +79,27 @@ namespace MTSC.UnitTests [TestMethod] public async Task ServerParsesRequestAndResponse() { - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:800"); - var response = await httpClient.GetAsync("some-module"); + var response = await httpClient.PostAsync("some-module/1234asvB9/test/99213/test", new StringContent(JsonConvert.SerializeObject(new HelloWorldMessage { HelloWorld = true }))); Assert.AreEqual(response.StatusCode, HttpStatusCode.OK); } [TestMethod] public async Task ServerRespondsDuringLongRunningTask() { - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:800"); var longRunningTask = httpClient.GetAsync("long-running"); - int responses = 0; - HttpClient client2 = new HttpClient(); + var responses = 0; + var client2 = new HttpClient(); client2.BaseAddress = new Uri("http://localhost:800"); while (!longRunningTask.IsCompleted) { _ = await client2.GetAsync(""); responses++; } + var result = longRunningTask.Result; Assert.AreEqual(result.StatusCode, HttpStatusCode.OK); Assert.IsTrue(responses > 5); @@ -106,7 +108,7 @@ namespace MTSC.UnitTests [TestMethod] public void HelloWorldHTTP() { - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:800"); var result = httpClient.GetAsync("").Result; Assert.AreEqual(result.StatusCode, HttpStatusCode.OK); @@ -115,9 +117,9 @@ namespace MTSC.UnitTests [TestMethod] public void MultipleRequestsShouldRespond() { - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:800"); - for (int i = 0; i < 10; i++) + for (var i = 0; i < 10; i++) { var result = httpClient.GetAsync("").GetAwaiter().GetResult(); Assert.AreEqual(result.StatusCode, HttpStatusCode.OK); @@ -127,37 +129,38 @@ namespace MTSC.UnitTests [TestMethod] public void SendFragmentedHttpMessage() { - Client.Client client = new Client.Client(); + var client = new Client.Client(); var notifyHandler = new NotifyReceivedMessageHandler(); - notifyHandler.ReceivedMessage += (o, m) => { receivedMessage = m.MessageBytes; }; + notifyHandler.ReceivedMessage += (o, m) => { this.receivedMessage = m.MessageBytes; }; client.SetServerAddress("127.0.0.1") .SetPort(800) .AddHandler(notifyHandler) .WithSsl(false) .Connect(); - HttpRequest request = new HttpRequest(); + var request = new HttpRequest(); request.Method = HttpMessage.HttpMethods.Post; request.BodyString = "Brought a message to you my guy!"; request.RequestURI = "/echo"; request.Headers[HttpMessage.EntityHeaders.ContentLength] = request.BodyString.Length.ToString(); - byte[] message = request.GetPackedRequest(); + var message = request.GetPackedRequest(); client.QueueMessage(message.Take(message.Length - request.BodyString.Length).ToArray()); Thread.Sleep(1000); client.QueueMessage(message.Skip(message.Length - request.BodyString.Length).Take(10).ToArray()); Thread.Sleep(1000); client.QueueMessage(message.Skip(message.Length - request.BodyString.Length + 10).Take(request.BodyString.Length - 10).ToArray()); - Stopwatch sw = new Stopwatch(); + var sw = new Stopwatch(); sw.Start(); - while (receivedMessage == null) + while (this.receivedMessage == null) { if(sw.ElapsedMilliseconds > 15000) { throw new Exception("Response not received!"); } } - HttpResponse response = HttpResponse.FromBytes(receivedMessage); + + var response = HttpResponse.FromBytes(this.receivedMessage); //Trim the null bytes from encryption/decryption response.BodyString = response.BodyString.Trim('\0'); Assert.AreEqual(response.StatusCode, HttpMessage.StatusCodes.OK); @@ -167,90 +170,93 @@ namespace MTSC.UnitTests [TestMethod] public void SendFragmentedHttpMessageShouldExpire() { - Client.Client client = new Client.Client(); + var client = new Client.Client(); var notifyHandler = new NotifyReceivedMessageHandler(); - notifyHandler.ReceivedMessage += (o, m) => { receivedMessage = m.MessageBytes; }; + notifyHandler.ReceivedMessage += (o, m) => { this.receivedMessage = m.MessageBytes; }; client.SetServerAddress("127.0.0.1") .SetPort(800) .AddHandler(notifyHandler) .WithSsl(false) .Connect(); - HttpRequest request = new HttpRequest(); + var request = new HttpRequest(); request.Method = HttpMessage.HttpMethods.Post; request.BodyString = "Brought a message to you my guy!"; request.RequestURI = "/echo"; request.Headers[HttpMessage.EntityHeaders.ContentLength] = request.BodyString.Length.ToString(); - byte[] message = request.GetPackedRequest(); + var message = request.GetPackedRequest(); client.QueueMessage(message.Take(message.Length - request.BodyString.Length).ToArray()); Thread.Sleep(300); client.QueueMessage(message.Skip(message.Length - request.BodyString.Length).Take(10).ToArray()); Thread.Sleep(3300); client.QueueMessage(message.Skip(message.Length - request.BodyString.Length + 10).Take(request.BodyString.Length - 10).ToArray()); - Stopwatch sw = new Stopwatch(); + var sw = new Stopwatch(); sw.Start(); - while (receivedMessage == null) + while (this.receivedMessage == null) { if (sw.ElapsedMilliseconds > 5000) { Assert.Fail("Should receive that message has expired!"); } } - var response = HttpResponse.FromBytes(receivedMessage); + + var response = HttpResponse.FromBytes(this.receivedMessage); Assert.AreEqual(response.StatusCode, HttpMessage.StatusCodes.BadRequest); } [TestMethod] public void SendFragmentedHttpMessageExceedingSizeShouldExpire() { - Client.Client client = new Client.Client(); + var client = new Client.Client(); var notifyHandler = new NotifyReceivedMessageHandler(); - notifyHandler.ReceivedMessage += (o, m) => { receivedMessage = m.MessageBytes; }; + notifyHandler.ReceivedMessage += (o, m) => { this.receivedMessage = m.MessageBytes; }; client.SetServerAddress("127.0.0.1") .SetPort(800) .AddHandler(notifyHandler) .WithSsl(false) .Connect(); - HttpRequest request = new HttpRequest(); + var request = new HttpRequest(); request.Method = HttpMessage.HttpMethods.Post; request.BodyString = "Brought a message to you my guy!"; request.RequestURI = "/echo"; request.Headers[HttpMessage.EntityHeaders.ContentLength] = request.BodyString.Length.ToString(); request.Body = new byte[255000]; Array.Fill(request.Body, 50); - byte[] message = request.GetPackedRequest(); + var message = request.GetPackedRequest(); client.QueueMessage(message.Take(message.Length - request.BodyString.Length).ToArray()); Thread.Sleep(5); client.QueueMessage(message.Skip(message.Length - request.BodyString.Length).Take(10).ToArray()); Thread.Sleep(5); client.QueueMessage(message.Skip(message.Length - request.BodyString.Length + 10).Take(request.BodyString.Length - 10).ToArray()); - Stopwatch sw = new Stopwatch(); + var sw = new Stopwatch(); sw.Start(); - while (receivedMessage == null) + while (this.receivedMessage == null) { if (sw.ElapsedMilliseconds > 15000) { Assert.Fail("Should receive that message has exceeded size!"); } } - var response = HttpResponse.FromBytes(receivedMessage); + + var response = HttpResponse.FromBytes(this.receivedMessage); Assert.AreNotEqual(response.StatusCode, HttpMessage.StatusCodes.OK); } [TestMethod] public void SendLargeHttpMessage() { - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:800"); httpClient.DefaultRequestHeaders.ExpectContinue = true; - string s = string.Empty; - for(int i = 0; i < 200000; i++) + var s = string.Empty; + for(var i = 0; i < 200000; i++) { s += "C"; } + using(var sc = new ByteArrayContent(Encoding.UTF8.GetBytes(s))) { var response = httpClient.PostAsync("echo", sc).Result; @@ -262,9 +268,9 @@ namespace MTSC.UnitTests [TestMethod] public void UploadFileShouldSucceed() { - byte[] bytes = new byte[120000]; + var bytes = new byte[120000]; - for(int i = 0; i < bytes.Length; i++) + for(var i = 0; i < bytes.Length; i++) { bytes[i] = 43; } @@ -291,9 +297,9 @@ namespace MTSC.UnitTests query["key1"] = "value1"; query["key2"] = "value2"; builder.Query = query.ToString(); - string url = builder.ToString(); + var url = builder.ToString(); - HttpClient httpClient = new HttpClient(); + var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri(url); var result = httpClient.GetAsync("").Result; Assert.AreEqual(result.StatusCode, HttpStatusCode.OK); @@ -305,7 +311,7 @@ namespace MTSC.UnitTests public async Task EchoWebsocket(string endpoint) { var bytes = new byte[100]; - ClientWebSocket client = new ClientWebSocket(); + var client = new ClientWebSocket(); await client.ConnectAsync(new Uri($"ws://localhost:800/{endpoint}"), CancellationToken.None); await client.SendAsync(Encoding.ASCII.GetBytes("Hello world!"), WebSocketMessageType.Text, true, CancellationToken.None); await client.ReceiveAsync(bytes, CancellationToken.None); diff --git a/MTSC.UnitTests/ExtendedUrlTests.cs b/MTSC.UnitTests/ExtendedUrlTests.cs new file mode 100644 index 0000000..4b9f2a7 --- /dev/null +++ b/MTSC.UnitTests/ExtendedUrlTests.cs @@ -0,0 +1,50 @@ +using System.Linq; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MTSC.Common; + +namespace MTSC.UnitTests +{ + [TestClass] + public sealed class ExtendedUrlTests + { + private const string Placeholder1 = "{placeholder1}"; + private const string Placeholder2 = "{placeholder2}"; + private const string Value1 = "12345123"; + private const string Value2 = "jdddw+_@#%!*@($&&@50*^23 + diff --git a/MTSC.UnitTests/MultipartModule.cs b/MTSC.UnitTests/MultipartModule.cs index 55c03b6..b0833c6 100644 --- a/MTSC.UnitTests/MultipartModule.cs +++ b/MTSC.UnitTests/MultipartModule.cs @@ -14,6 +14,7 @@ namespace MTSC.UnitTests { return Task.FromResult(new HttpResponse { StatusCode = HttpMessage.StatusCodes.OK }); } + return Task.FromResult(new HttpResponse { StatusCode = HttpMessage.StatusCodes.BadRequest }); } } diff --git a/MTSC.UnitTests/RoutingModules/SomeRequestConverter.cs b/MTSC.UnitTests/RoutingModules/SomeRequestConverter.cs index c792f9d..c86d94d 100644 --- a/MTSC.UnitTests/RoutingModules/SomeRequestConverter.cs +++ b/MTSC.UnitTests/RoutingModules/SomeRequestConverter.cs @@ -1,13 +1,30 @@ -using MTSC.Common.Http; -using MTSC.Common.Http.RoutingModules; +using System; +using System.ComponentModel; +using System.Globalization; +using MTSC.Common.Http; namespace MTSC.UnitTests.RoutingModules { - public class SomeRequestConverter : IRequestConverter + public class SomeRequestConverter : TypeConverter { - public SomeRoutingRequest ConvertHttpRequest(HttpRequest httpRequest) + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - return new SomeRoutingRequest(); + if (sourceType == typeof(HttpRequest)) + { + return true; + } + + return base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is HttpRequest) + { + return new SomeRoutingRequest(); + } + + return base.ConvertFrom(context, culture, value); } } } diff --git a/MTSC.UnitTests/RoutingModules/SomeResponseConverter.cs b/MTSC.UnitTests/RoutingModules/SomeResponseConverter.cs index 773443f..868b816 100644 --- a/MTSC.UnitTests/RoutingModules/SomeResponseConverter.cs +++ b/MTSC.UnitTests/RoutingModules/SomeResponseConverter.cs @@ -1,13 +1,31 @@ -using MTSC.Common.Http; +using System; +using System.ComponentModel; +using System.Globalization; +using MTSC.Common.Http; using MTSC.Common.Http.RoutingModules; namespace MTSC.UnitTests.RoutingModules { - public class SomeResponseConverter : IResponseConverter + public class SomeResponseConverter : TypeConverter { - public HttpResponse ConvertResponse(SomeRoutingResponse response) + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - return new HttpResponse { StatusCode = HttpMessage.StatusCodes.OK }; + if (destinationType == typeof(SomeRoutingResponse)) + { + return true; + } + + return base.CanConvertTo(context, destinationType); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value is SomeRoutingResponse) + { + return new HttpResponse { StatusCode = HttpMessage.StatusCodes.OK }; + } + + return base.ConvertTo(context, culture, value, destinationType); } } } diff --git a/MTSC.UnitTests/RoutingModules/SomeRoutingModule.cs b/MTSC.UnitTests/RoutingModules/SomeRoutingModule.cs index 6a91963..47f377f 100644 --- a/MTSC.UnitTests/RoutingModules/SomeRoutingModule.cs +++ b/MTSC.UnitTests/RoutingModules/SomeRoutingModule.cs @@ -1,10 +1,20 @@ -using MTSC.Common.Http.RoutingModules; +using MTSC.Common.Http.Attributes; +using MTSC.Common.Http.RoutingModules; using System.Threading.Tasks; namespace MTSC.UnitTests.RoutingModules { public class SomeRoutingModule : HttpRouteBase { + [FromUrl("someValue")] + public string SomeValue { get; } + [FromUrl("intValue")] + public int IntValue { get; } + [FromBody] + public HelloWorldMessage HelloWorldMessage { get; } + [FromHeaders("Content-Length")] + public int ContentLength { get; } + public override Task HandleRequest(SomeRoutingRequest request) { return Task.FromResult(new SomeRoutingResponse()); diff --git a/MTSC.UnitTests/RoutingModules/SomeRoutingRequest.cs b/MTSC.UnitTests/RoutingModules/SomeRoutingRequest.cs index 3e6bdcb..066ecb0 100644 --- a/MTSC.UnitTests/RoutingModules/SomeRoutingRequest.cs +++ b/MTSC.UnitTests/RoutingModules/SomeRoutingRequest.cs @@ -1,8 +1,9 @@ -using MTSC.Common.Http.RoutingModules; +using System.ComponentModel; +using MTSC.Common.Http.RoutingModules; namespace MTSC.UnitTests.RoutingModules { - [RequestConvert(typeof(SomeRequestConverter))] + [TypeConverter(typeof(SomeRequestConverter))] public class SomeRoutingRequest { } diff --git a/MTSC.UnitTests/RoutingModules/SomeRoutingResponse.cs b/MTSC.UnitTests/RoutingModules/SomeRoutingResponse.cs index 070baac..935a0cb 100644 --- a/MTSC.UnitTests/RoutingModules/SomeRoutingResponse.cs +++ b/MTSC.UnitTests/RoutingModules/SomeRoutingResponse.cs @@ -1,8 +1,9 @@ -using MTSC.Common.Http.RoutingModules; +using System.ComponentModel; +using MTSC.Common.Http.RoutingModules; namespace MTSC.UnitTests.RoutingModules { - [ResponseConvert(typeof(SomeResponseConverter))] + [TypeConverter(typeof(SomeResponseConverter))] public class SomeRoutingResponse { } diff --git a/MTSC.UnitTests/ServerTests.cs b/MTSC.UnitTests/ServerTests.cs index 02d0125..ec74f52 100644 --- a/MTSC.UnitTests/ServerTests.cs +++ b/MTSC.UnitTests/ServerTests.cs @@ -11,7 +11,7 @@ namespace MTSC.UnitTests [TestMethod] public void Stop() { - Server server = new Server(); + var server = new Server(); server.RunAsync(); Thread.Sleep(1000); Assert.IsTrue(server.Running); diff --git a/MTSC/ClientSide/Client.cs b/MTSC/ClientSide/Client.cs index 70cbc8d..30ede81 100644 --- a/MTSC/ClientSide/Client.cs +++ b/MTSC/ClientSide/Client.cs @@ -24,10 +24,10 @@ namespace MTSC.Client #region Fields private TcpClient tcpClient; private CancellationTokenSource cancelMonitorToken; - private readonly List handlers = new List(); - private readonly List loggers = new List(); - private readonly List exceptionHandlers = new List(); - private readonly Queue messageQueue = new Queue(); + private readonly List handlers = new(); + private readonly List loggers = new(); + private readonly List exceptionHandlers = new(); + private readonly Queue messageQueue = new(); private SslStream sslStream = null; private TimeoutSuppressedStream safeNetworkStream = null; #endregion @@ -36,10 +36,14 @@ namespace MTSC.Client { get { - if (tcpClient != null) - return tcpClient.Connected; + if (this.tcpClient != null) + { + return this.tcpClient.Connected; + } else + { return false; + } } } public string Address { get; private set; } @@ -111,7 +115,7 @@ namespace MTSC.Client /// Message to be sent. public void QueueMessage(byte[] message) { - messageQueue.Enqueue(message); + this.messageQueue.Enqueue(message); } /// /// Logs the message onto the associated loggers. @@ -119,7 +123,7 @@ namespace MTSC.Client /// Message to be logged. public void Log(string log) { - foreach (ILogger logger in loggers) + foreach (var logger in this.loggers) { logger.Log(log); } @@ -130,7 +134,7 @@ namespace MTSC.Client /// public void LogDebug(string debugMessage) { - foreach (ILogger logger in loggers) + foreach (var logger in this.loggers) { logger.LogDebug(debugMessage); } @@ -162,7 +166,7 @@ namespace MTSC.Client /// This client object. public Client AddHandler(IHandler handler) { - handlers.Add(handler); + this.handlers.Add(handler); return this; } /// @@ -172,7 +176,7 @@ namespace MTSC.Client /// This client object. public Client AddExceptionHandler(IExceptionHandler handler) { - exceptionHandlers.Add(handler); + this.exceptionHandlers.Add(handler); return this; } /// @@ -182,7 +186,7 @@ namespace MTSC.Client /// This client object. public Client AddLogger(ILogger logger) { - loggers.Add(logger); + this.loggers.Add(logger); return this; } /// @@ -219,6 +223,7 @@ namespace MTSC.Client { shouldUseSsl = true; } + this.Address = addressUri.Host; } @@ -232,12 +237,12 @@ namespace MTSC.Client this.sslStream.AuthenticateAsClient(this.Address); } - foreach(ILogger logger in this.loggers) + foreach(var logger in this.loggers) { logger.Log("Connected to: " + this.tcpClient.Client.RemoteEndPoint.ToString()); } - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { if (!handler.InitializeConnection(this)) { @@ -246,17 +251,18 @@ namespace MTSC.Client } this.cancelMonitorToken = new CancellationTokenSource(); - Task.Run(new Action(MonitorConnection), this.cancelMonitorToken.Token); + Task.Run(new Action(this.MonitorConnection), this.cancelMonitorToken.Token); return true; } catch(Exception e) { - LogDebug("Exception: " + e.Message); - LogDebug("Stacktrace: " + e.StackTrace); - foreach (IExceptionHandler exceptionHandler in this.exceptionHandlers) + this.LogDebug("Exception: " + e.Message); + this.LogDebug("Stacktrace: " + e.StackTrace); + foreach (var exceptionHandler in this.exceptionHandlers) { exceptionHandler.HandleException(e); } + return false; } } @@ -266,7 +272,7 @@ namespace MTSC.Client /// True if the connection was successful. public Task ConnectAsync() { - return Task.Run(Connect); + return Task.Run(this.Connect); } /// /// Disconnects from the server. @@ -289,7 +295,9 @@ namespace MTSC.Client private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (sslPolicyErrors == SslPolicyErrors.None) + { return true; + } return true; } @@ -318,6 +326,7 @@ namespace MTSC.Client { this.HandleException(e); } + Thread.Sleep(33); } @@ -339,13 +348,14 @@ namespace MTSC.Client { if (this.messageQueue.Count > 0) { - byte[] messagebytes = this.messageQueue.Dequeue(); - Message sendMessage = CommunicationPrimitives.BuildMessage(messagebytes); - for (int i = handlers.Count - 1; i >= 0; i--) + var messagebytes = this.messageQueue.Dequeue(); + var sendMessage = CommunicationPrimitives.BuildMessage(messagebytes); + for (var i = this.handlers.Count - 1; i >= 0; i--) { - IHandler handler = handlers[i]; + var handler = this.handlers[i]; handler.HandleSendMessage(this, ref sendMessage); } + CommunicationPrimitives.SendMessage(this.tcpClient, sendMessage, this.sslStream); } } @@ -358,7 +368,7 @@ namespace MTSC.Client * When a message has been received, process it. */ var message = CommunicationPrimitives.GetMessage(this.safeNetworkStream, this.sslStream); - LogDebug("Received a message of size: " + message.MessageLength); + this.LogDebug("Received a message of size: " + message.MessageLength); this.PrehandleReceivedMessage(message); this.HandleReceivedMessage(message); } @@ -366,7 +376,7 @@ namespace MTSC.Client private void PrehandleReceivedMessage(Message message) { - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { if (handler.PreHandleReceivedMessage(this, ref message)) { @@ -377,7 +387,7 @@ namespace MTSC.Client private void HandleReceivedMessage(Message message) { - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { if (handler.HandleReceivedMessage(this, message)) { @@ -388,7 +398,7 @@ namespace MTSC.Client private void TickHandlers() { - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { handler.Tick(this); } @@ -396,9 +406,9 @@ namespace MTSC.Client private void HandleException(Exception exception) { - LogDebug("Exception: " + exception.Message); - LogDebug("Stacktrace: " + exception.StackTrace); - foreach (IExceptionHandler exceptionHandler in this.exceptionHandlers) + this.LogDebug("Exception: " + exception.Message); + this.LogDebug("Stacktrace: " + exception.StackTrace); + foreach (var exceptionHandler in this.exceptionHandlers) { exceptionHandler.HandleException(exception); } diff --git a/MTSC/ClientSide/Handlers/BroadcastHandler.cs b/MTSC/ClientSide/Handlers/BroadcastHandler.cs index 51af802..c839c93 100644 --- a/MTSC/ClientSide/Handlers/BroadcastHandler.cs +++ b/MTSC/ClientSide/Handlers/BroadcastHandler.cs @@ -5,7 +5,7 @@ namespace MTSC.Client.Handlers { public sealed class BroadcastHandler : IHandler { - private List buffer = new List(); + private List buffer = new(); /// /// Creates a new instance of BroadcastHandler. /// diff --git a/MTSC/ClientSide/Handlers/EncryptionHandler.cs b/MTSC/ClientSide/Handlers/EncryptionHandler.cs index e60a479..b83b0fe 100644 --- a/MTSC/ClientSide/Handlers/EncryptionHandler.cs +++ b/MTSC/ClientSide/Handlers/EncryptionHandler.cs @@ -35,16 +35,17 @@ namespace MTSC.Client.Handlers #region Private Methods private string GetUniqueKey(int size) { - return Convert.ToBase64String(GetUniqueByteKey(size)); + return Convert.ToBase64String(this.GetUniqueByteKey(size)); } private byte[] GetUniqueByteKey(int size) { - byte[] data = new byte[size]; - using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider()) + var data = new byte[size]; + using (var crypto = new RNGCryptoServiceProvider()) { crypto.GetBytes(data); } + return data; } @@ -54,16 +55,16 @@ namespace MTSC.Client.Handlers // Set your salt here, change it to meet your flavor: // The salt bytes must be at least 8 bytes. - byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + var saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { - using (RijndaelManaged AES = new RijndaelManaged()) + using (var AES = new RijndaelManaged()) { AES.KeySize = 256; AES.BlockSize = 128; AES.Mode = CipherMode.CBC; - var key = new Rfc2898DeriveBytes(aesKey, saltBytes, 1000); + var key = new Rfc2898DeriveBytes(this.aesKey, saltBytes, 1000); AES.Key = key.GetBytes(AES.KeySize / 8); AES.IV = key.GetBytes(AES.BlockSize / 8); @@ -72,6 +73,7 @@ namespace MTSC.Client.Handlers cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length); cs.Close(); } + encryptedBytes = ms.ToArray(); } } @@ -85,18 +87,18 @@ namespace MTSC.Client.Handlers // Set your salt here, change it to meet your flavor: // The salt bytes must be at least 8 bytes. - byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + var saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { - using (RijndaelManaged AES = new RijndaelManaged()) + using (var AES = new RijndaelManaged()) { AES.KeySize = 256; AES.BlockSize = 128; AES.Mode = CipherMode.CBC; - var key = new Rfc2898DeriveBytes(aesKey, saltBytes, 1000); + var key = new Rfc2898DeriveBytes(this.aesKey, saltBytes, 1000); AES.Key = key.GetBytes(AES.KeySize / 8); AES.IV = key.GetBytes(AES.BlockSize / 8); @@ -107,6 +109,7 @@ namespace MTSC.Client.Handlers cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length); cs.Close(); } + decryptedBytes = ms.ToArray(); } } @@ -117,11 +120,11 @@ namespace MTSC.Client.Handlers private string EncryptText(string input) { // Get the bytes of the string - byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input); + var bytesToBeEncrypted = Encoding.UTF8.GetBytes(input); - byte[] bytesEncrypted = EncryptBytes(bytesToBeEncrypted); + var bytesEncrypted = this.EncryptBytes(bytesToBeEncrypted); - string result = Encoding.UTF8.GetString(bytesEncrypted); + var result = Encoding.UTF8.GetString(bytesEncrypted); return result; } @@ -129,11 +132,11 @@ namespace MTSC.Client.Handlers private string DecryptText(string input) { // Get the bytes of the string - byte[] bytesToBeDecrypted = Encoding.UTF8.GetBytes(input); + var bytesToBeDecrypted = Encoding.UTF8.GetBytes(input); - byte[] bytesDecrypted = DecryptBytes(bytesToBeDecrypted); + var bytesDecrypted = this.DecryptBytes(bytesToBeDecrypted); - string result = Encoding.UTF8.GetString(bytesDecrypted); + var result = Encoding.UTF8.GetString(bytesDecrypted); return result; } @@ -165,13 +168,14 @@ namespace MTSC.Client.Handlers /// True if no other handler should process the message further. bool IHandler.HandleSendMessage(Client client, ref Message message) { - if (connectionState == ConnectionState.Encrypted) + if (this.connectionState == ConnectionState.Encrypted) { - byte[] decryptedBytes = message.MessageBytes; - byte[] encryptedBytes = EncryptBytes(decryptedBytes); + var decryptedBytes = message.MessageBytes; + var encryptedBytes = this.EncryptBytes(decryptedBytes); message = CommunicationPrimitives.BuildMessage(encryptedBytes); return true; } + return false; } /// @@ -182,13 +186,14 @@ namespace MTSC.Client.Handlers /// True if no other handler should process it further. bool IHandler.PreHandleReceivedMessage(Client client, ref Message message) { - if (connectionState == ConnectionState.Encrypted) + if (this.connectionState == ConnectionState.Encrypted) { - byte[] encryptedBytes = message.MessageBytes; - byte[] decryptedBytes = DecryptBytes(encryptedBytes); + var encryptedBytes = message.MessageBytes; + var decryptedBytes = this.DecryptBytes(encryptedBytes); message = CommunicationPrimitives.BuildMessage(decryptedBytes); return false; } + return false; } /// @@ -199,17 +204,17 @@ namespace MTSC.Client.Handlers /// True if no other handler should handle the message further. bool IHandler.HandleReceivedMessage(Client client, Message message) { - if (connectionState == ConnectionState.RequestingPublicKey) + if (this.connectionState == ConnectionState.RequestingPublicKey) { - string ascii = ASCIIEncoding.ASCII.GetString(message.MessageBytes); + var ascii = ASCIIEncoding.ASCII.GetString(message.MessageBytes); if (ascii.Contains(CommunicationPrimitives.SendPublicKey)) { - byte[] publicKeyBytes = new byte[message.MessageLength - CommunicationPrimitives.SendPublicKey.Length - 1]; + var publicKeyBytes = new byte[message.MessageLength - CommunicationPrimitives.SendPublicKey.Length - 1]; Array.Copy(message.MessageBytes, CommunicationPrimitives.SendPublicKey.Length + 1, publicKeyBytes, 0, publicKeyBytes.Length); - string publicKey = ASCIIEncoding.ASCII.GetString(publicKeyBytes); - RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024); - RSAParameters parameters = new RSAParameters(); - XmlDocument xmlDoc = new XmlDocument(); + var publicKey = ASCIIEncoding.ASCII.GetString(publicKeyBytes); + var rsa = new RSACryptoServiceProvider(1024); + var parameters = new RSAParameters(); + var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(publicKey); if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue")) { @@ -228,33 +233,35 @@ namespace MTSC.Client.Handlers } } } + rsa.ImportParameters(parameters); - byte[] symkeyBytes = GetUniqueByteKey(32); - byte[] encryptedSymKey = rsa.Encrypt(symkeyBytes, false); - aesKey = symkeyBytes; - byte[] messageHeader = ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.SendEncryptionKey + ":"); - byte[] messageBytes = new byte[encryptedSymKey.Length + messageHeader.Length]; + var symkeyBytes = this.GetUniqueByteKey(32); + var encryptedSymKey = rsa.Encrypt(symkeyBytes, false); + this.aesKey = symkeyBytes; + var messageHeader = ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.SendEncryptionKey + ":"); + var messageBytes = new byte[encryptedSymKey.Length + messageHeader.Length]; Array.Copy(messageHeader, 0, messageBytes, 0, messageHeader.Length); Array.Copy(encryptedSymKey, 0, messageBytes, messageHeader.Length, encryptedSymKey.Length); client.QueueMessage(messageBytes); - connectionState = ConnectionState.NegotiatingSymKey; + this.connectionState = ConnectionState.NegotiatingSymKey; return true; } } - else if (connectionState == ConnectionState.NegotiatingSymKey) + else if (this.connectionState == ConnectionState.NegotiatingSymKey) { - string ascii = ASCIIEncoding.ASCII.GetString(DecryptBytes(message.MessageBytes)); + var ascii = ASCIIEncoding.ASCII.GetString(this.DecryptBytes(message.MessageBytes)); if (ascii == CommunicationPrimitives.AcceptEncryptionKey) { - connectionState = ConnectionState.Encrypted; + this.connectionState = ConnectionState.Encrypted; return true; } else { - connectionState = ConnectionState.Initial; + this.connectionState = ConnectionState.Initial; return false; } } + return false; } /// @@ -264,7 +271,7 @@ namespace MTSC.Client.Handlers /// Client connection. void IHandler.Tick(Client client) { - if (connectionState == ConnectionState.Initial) + if (this.connectionState == ConnectionState.Initial) { client.QueueMessage(ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.RequestPublicKey)); diff --git a/MTSC/ClientSide/Handlers/HttpHandler.cs b/MTSC/ClientSide/Handlers/HttpHandler.cs index 7a854ec..b85530d 100644 --- a/MTSC/ClientSide/Handlers/HttpHandler.cs +++ b/MTSC/ClientSide/Handlers/HttpHandler.cs @@ -10,7 +10,7 @@ namespace MTSC.Client.Handlers public sealed class HttpHandler : IHandler { #region Fields - List httpModules = new List(); + List httpModules = new(); #endregion #region Constructors public HttpHandler() @@ -34,7 +34,7 @@ namespace MTSC.Client.Handlers /// This handler object. public HttpHandler AddModule(IHttpModule httpModule) { - httpModules.Add(httpModule); + this.httpModules.Add(httpModule); return this; } #endregion @@ -55,14 +55,15 @@ namespace MTSC.Client.Handlers /// False. bool IHandler.HandleReceivedMessage(Client client, Message message) { - HttpResponse httpMessage = new HttpResponse(message.MessageBytes); - foreach(IHttpModule httpModule in httpModules) + var httpMessage = new HttpResponse(message.MessageBytes); + foreach(var httpModule in this.httpModules) { if (httpModule.HandleResponse(this, httpMessage)) { break; } } + return false; } /// diff --git a/MTSC/ClientSide/Handlers/WebsocketHandler.cs b/MTSC/ClientSide/Handlers/WebsocketHandler.cs index 4ec038e..87f60d7 100644 --- a/MTSC/ClientSide/Handlers/WebsocketHandler.cs +++ b/MTSC/ClientSide/Handlers/WebsocketHandler.cs @@ -29,8 +29,8 @@ namespace MTSC.Client.Handlers #region Fields SocketState state = SocketState.Initial; - Queue messageQueue = new Queue(); - List websocketModules = new List(); + Queue messageQueue = new(); + List websocketModules = new(); string expectedguid = string.Empty; #endregion #region Properties @@ -39,7 +39,7 @@ namespace MTSC.Client.Handlers #region Constructors public WebsocketHandler() { - WebsocketURI = "/"; + this.WebsocketURI = "/"; } #endregion #region Public Methods @@ -50,7 +50,7 @@ namespace MTSC.Client.Handlers /// This handler object. public WebsocketHandler AddModule(IWebsocketModule websocketModule) { - websocketModules.Add(websocketModule); + this.websocketModules.Add(websocketModule); return this; } /// @@ -59,7 +59,7 @@ namespace MTSC.Client.Handlers /// Message to be sent. public void QueueMessage(WebsocketMessage message) { - messageQueue.Enqueue(message); + this.messageQueue.Enqueue(message); } #endregion #region Handler Implementation @@ -70,17 +70,17 @@ namespace MTSC.Client.Handlers bool IHandler.HandleReceivedMessage(Client client, Message message) { - if(state == SocketState.Handshaking) + if(this.state == SocketState.Handshaking) { - HttpResponse response = new HttpResponse(message.MessageBytes); + var response = new HttpResponse(message.MessageBytes); if(response.StatusCode == HttpMessage.StatusCodes.SwitchingProtocols && response.Headers["Upgrade"] == "websocket" && response.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" && - response.Headers[WebsocketHeaderAcceptKey].Trim() == expectedguid) + response.Headers[WebsocketHeaderAcceptKey].Trim() == this.expectedguid) { - state = SocketState.Established; + this.state = SocketState.Established; client.LogDebug($"Websocket handshake completed!"); - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { websocketModule.ConnectionInitialized(client, this); } @@ -89,23 +89,24 @@ namespace MTSC.Client.Handlers { client.LogDebug($"Expected websocket handshake response. Got {response.StatusCode}"); } + return true; } - else if(state == SocketState.Established) + else if(this.state == SocketState.Established) { - WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes); + var receivedMessage = new WebsocketMessage(message.MessageBytes); client.LogDebug($"Received websocket message of length {message.MessageBytes.Length}."); if (receivedMessage.Opcode == WebsocketMessage.Opcodes.Close) { client.LogDebug("Closing websocket"); - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { websocketModule.ConnectionClosed(client, this); } } else { - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { if (websocketModule.HandleReceivedMessage(client, this, receivedMessage)) { @@ -113,8 +114,10 @@ namespace MTSC.Client.Handlers } } } + return true; } + return false; } @@ -125,13 +128,13 @@ namespace MTSC.Client.Handlers bool IHandler.InitializeConnection(Client client) { - state = SocketState.Handshaking; - string handshakeGuid = Guid.NewGuid().ToString(); - string handshakeKey = handshakeGuid+ GlobalUniqueIdentifier; - expectedguid = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey))); - HttpRequest beginRequest = new HttpRequest(); + this.state = SocketState.Handshaking; + var handshakeGuid = Guid.NewGuid().ToString(); + var handshakeKey = handshakeGuid+ GlobalUniqueIdentifier; + this.expectedguid = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey))); + var beginRequest = new HttpRequest(); beginRequest.Method = HttpMessage.HttpMethods.Get; - beginRequest.RequestURI = WebsocketURI; + beginRequest.RequestURI = this.WebsocketURI; beginRequest.Headers[HttpMessage.RequestHeaders.Host] = client.Address; beginRequest.Headers[HttpMessage.GeneralHeaders.Connection] = "Upgrade"; beginRequest.Headers[HttpMessage.GeneralHeaders.Upgrade] = "websocket"; @@ -151,14 +154,14 @@ namespace MTSC.Client.Handlers void IHandler.Tick(Client client) { - while(messageQueue.Count > 0) + while(this.messageQueue.Count > 0) { - WebsocketMessage message = messageQueue.Dequeue(); + var message = this.messageQueue.Dequeue(); client.QueueMessage(message.GetMessageBytes()); if(message.Opcode == WebsocketMessage.Opcodes.Close) { client.LogDebug("Closing websocket connection"); - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { websocketModule.ConnectionClosed(client, this); } diff --git a/MTSC/Common/ExtendedUrl.cs b/MTSC/Common/ExtendedUrl.cs new file mode 100644 index 0000000..67edd5a --- /dev/null +++ b/MTSC/Common/ExtendedUrl.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace MTSC.Common +{ + internal sealed class ExtendedUrl + { + internal sealed class UrlComponent + { + public string Content { get; set; } + public bool IsPlaceholder { get; set; } + } + + private readonly List components = new(); + + public string Url { get; } + + public ExtendedUrl(string url) + { + this.Url = url; + this.ParseUrl(); + } + + public bool TryMatchUrl(string url, out List urlValues) + { + urlValues = null; + try + { + var matchingUrlComponents = SplitIntoComponents(url).ToList(); + var values = new List(); + if (matchingUrlComponents.Count != this.components.Count) + { + return false; + } + + for(var i = 0; i < matchingUrlComponents.Count; i++) + { + var matchingComponent = matchingUrlComponents[i]; + var component = this.components[i]; + if (component.IsPlaceholder) + { + values.Add(new UrlValue { Placeholder = component.Content.TrimStart('{').TrimEnd('}'), Value = matchingComponent.Content }); + } + else if (component.Content != matchingComponent.Content) + { + return false; + } + } + + urlValues = values; + return true; + } + catch + { + return false; + } + } + + private void ParseUrl() + { + this.components.AddRange(SplitIntoComponents(this.Url)); + foreach (var component in this.components) + { + if (component.Content.First() == '{' && component.Content.Last() == '}') + { + component.IsPlaceholder = true; + } + } + } + + private static IEnumerable SplitIntoComponents(string url) + { + return url.Split('/').Where(token => string.IsNullOrWhiteSpace(token) is false).Select(token => new UrlComponent { Content = token }); + } + } +} diff --git a/MTSC/Common/Ftp/FtpData.cs b/MTSC/Common/Ftp/FtpData.cs index 432df44..0786221 100644 --- a/MTSC/Common/Ftp/FtpData.cs +++ b/MTSC/Common/Ftp/FtpData.cs @@ -21,6 +21,7 @@ namespace MTSC.Common.Ftp this.TransferDetails.Socket.Dispose(); this.TransferDetails.Socket = dataConnection; } + this.TransferDetails.ConnectionOpen = true; } diff --git a/MTSC/Common/Ftp/FtpModules/AuthenticationModule.cs b/MTSC/Common/Ftp/FtpModules/AuthenticationModule.cs index 704f118..baf309e 100644 --- a/MTSC/Common/Ftp/FtpModules/AuthenticationModule.cs +++ b/MTSC/Common/Ftp/FtpModules/AuthenticationModule.cs @@ -32,7 +32,7 @@ namespace MTSC.Common.Ftp.FtpModules if (!string.IsNullOrWhiteSpace(authData.Username)) { authData.Password = request.Arguments[0]; - if (ValidateAuthentication.Invoke(authData)) + if (this.ValidateAuthentication.Invoke(authData)) { handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.UserLoggedIn, Message = "User logged in!" }); authData.Authenticated = true; @@ -46,6 +46,7 @@ namespace MTSC.Common.Ftp.FtpModules { handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.NotLoggedIn, Message = "Username not present. Please provide username!" }); } + return true; } else if(!authData.Authenticated) @@ -53,6 +54,7 @@ namespace MTSC.Common.Ftp.FtpModules handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.NotLoggedIn, Message = "Please log in before issuing other commands!" }); return true; } + return false; } diff --git a/MTSC/Common/Ftp/FtpModules/DataConnectionModule.cs b/MTSC/Common/Ftp/FtpModules/DataConnectionModule.cs index 9e6300d..ea682e3 100644 --- a/MTSC/Common/Ftp/FtpModules/DataConnectionModule.cs +++ b/MTSC/Common/Ftp/FtpModules/DataConnectionModule.cs @@ -17,17 +17,17 @@ namespace MTSC.Common.Ftp.FtpModules if (request.Command == FtpRequestCommands.PASV) { - handler.QueueFtpResponse(server, client, EnablePassiveMode(request, ftpData, client)); + handler.QueueFtpResponse(server, client, this.EnablePassiveMode(request, ftpData, client)); return true; } else if (request.Command == FtpRequestCommands.PORT) { - handler.QueueFtpResponse(server, client, EnableActiveMode(request, ftpData)); + handler.QueueFtpResponse(server, client, this.EnableActiveMode(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.TYPE) { - handler.QueueFtpResponse(server, client, HandleTypeArguments(request, ftpData)); + handler.QueueFtpResponse(server, client, this.HandleTypeArguments(request, ftpData)); return true; } @@ -67,8 +67,8 @@ namespace MTSC.Common.Ftp.FtpModules ftpData.TransferDetails.Mode = TransferDetails.TransferMode.Passive; ftpData.TransferDetails.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ftpData.TransferDetails.Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); - byte[] address = ((IPEndPoint)client.TcpClient.Client.LocalEndPoint).Address.GetAddressBytes(); - byte[] portBytes = BitConverter.GetBytes((short)ftpData.TransferDetails.LocalDataPort); + var address = ((IPEndPoint)client.TcpClient.Client.LocalEndPoint).Address.GetAddressBytes(); + var portBytes = BitConverter.GetBytes((short)ftpData.TransferDetails.LocalDataPort); if (BitConverter.IsLittleEndian) { diff --git a/MTSC/Common/Ftp/FtpModules/DirectoryModule.cs b/MTSC/Common/Ftp/FtpModules/DirectoryModule.cs index 1c6935c..dbb50ab 100644 --- a/MTSC/Common/Ftp/FtpModules/DirectoryModule.cs +++ b/MTSC/Common/Ftp/FtpModules/DirectoryModule.cs @@ -21,15 +21,15 @@ namespace MTSC.Common.Ftp.FtpModules bool IFtpModule.HandleRequest(FtpRequest request, ClientData client, FtpHandler handler, Server server) { - if (!Directory.Exists(Path.GetFullPath(RootPath))) + if (!Directory.Exists(Path.GetFullPath(this.RootPath))) { - server.Log($"Root path [{Path.GetFullPath(RootPath)}] doesn't exist! Creating it now!"); - Directory.CreateDirectory(Path.GetFullPath(RootPath)); + server.Log($"Root path [{Path.GetFullPath(this.RootPath)}] doesn't exist! Creating it now!"); + Directory.CreateDirectory(Path.GetFullPath(this.RootPath)); } if (!client.Resources.TryGetResource(out var ftpData)) { - client.Resources.SetResource(new FtpData { CurrentDirectory = Path.GetFullPath(RootPath) }); + client.Resources.SetResource(new FtpData { CurrentDirectory = Path.GetFullPath(this.RootPath) }); } if (string.IsNullOrEmpty(ftpData.CurrentDirectory)) @@ -39,27 +39,27 @@ namespace MTSC.Common.Ftp.FtpModules if (request.Command == FtpRequestCommands.MKD) { - handler.QueueFtpResponse(server, client, CreateDirectory(request, ftpData)); + handler.QueueFtpResponse(server, client, this.CreateDirectory(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.PWD) { - handler.QueueFtpResponse(server, client, PrintCurrentDirectory(request, ftpData)); + handler.QueueFtpResponse(server, client, this.PrintCurrentDirectory(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.CWD) { - handler.QueueFtpResponse(server, client, ChangeCurrentDirectory(request, ftpData)); + handler.QueueFtpResponse(server, client, this.ChangeCurrentDirectory(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.CDUP) { - handler.QueueFtpResponse(server, client, ChangeToParentDirectory(request, ftpData)); + handler.QueueFtpResponse(server, client, this.ChangeToParentDirectory(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.RMD) { - handler.QueueFtpResponse(server, client, RemoveDirectory(request, ftpData)); + handler.QueueFtpResponse(server, client, this.RemoveDirectory(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.LIST) @@ -69,7 +69,8 @@ namespace MTSC.Common.Ftp.FtpModules handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.FileStatusOkay, Message = "Opening data connection" }); ftpData.OpenDataConnection(); } - handler.QueueFtpResponse(server, client, ListFolder(request, ftpData)); + + handler.QueueFtpResponse(server, client, this.ListFolder(request, ftpData)); ftpData.CloseDataConnection(); return true; } @@ -88,6 +89,7 @@ namespace MTSC.Common.Ftp.FtpModules { relativePath = relativePath.Substring(this.RootPath.Length) + "\\"; } + return new FtpResponse { StatusCode = FtpResponseCodes.PATHNAMECreated, Message = $"\"{relativePath}\" is current directory" }; } @@ -96,14 +98,14 @@ namespace MTSC.Common.Ftp.FtpModules var path = request.Arguments.Length > 0 ? Path.GetFullPath(ftpData.CurrentDirectory + request.Arguments.Aggregate((prev, next) => prev + " " + next)) : ftpData.CurrentDirectory; - IEnumerable directories = Directory.EnumerateDirectories(path); - IEnumerable files = Directory.EnumerateFiles(path); + var directories = Directory.EnumerateDirectories(path); + var files = Directory.EnumerateFiles(path); - foreach (string dir in directories) + foreach (var dir in directories) { - DateTime editDate = Directory.GetLastWriteTime(dir); + var editDate = Directory.GetLastWriteTime(dir); - string date = editDate < DateTime.Now.Subtract(TimeSpan.FromDays(180)) ? + var date = editDate < DateTime.Now.Subtract(TimeSpan.FromDays(180)) ? editDate.ToString("MMM dd yyyy", CultureInfo.InvariantCulture) : editDate.ToString("MMM dd HH:mm", CultureInfo.InvariantCulture); @@ -112,11 +114,11 @@ namespace MTSC.Common.Ftp.FtpModules ftpData.SendData(bytes, bytes.Length); } - foreach (string file in files) + foreach (var file in files) { - FileInfo f = new FileInfo(file); + var f = new FileInfo(file); - string date = f.LastWriteTime < DateTime.Now.Subtract(TimeSpan.FromDays(180)) ? + var date = f.LastWriteTime < DateTime.Now.Subtract(TimeSpan.FromDays(180)) ? f.LastWriteTime.ToString("MMM dd yyyy", CultureInfo.InvariantCulture) : f.LastWriteTime.ToString("MMM dd HH:mm", CultureInfo.InvariantCulture); @@ -126,7 +128,7 @@ namespace MTSC.Common.Ftp.FtpModules if (length.Length < 8) { - for (int i = 0; i < 8 - length.Length; i++) + for (var i = 0; i < 8 - length.Length; i++) { line += ' '; } @@ -136,6 +138,7 @@ namespace MTSC.Common.Ftp.FtpModules var bytes = Encoding.UTF8.GetBytes(line); ftpData.SendData(bytes, bytes.Length); } + return new FtpResponse { StatusCode = FtpResponseCodes.ClosingDataConnection, Message = "Transfer complete" }; } @@ -193,7 +196,7 @@ namespace MTSC.Common.Ftp.FtpModules private FtpResponse ChangeToParentDirectory(FtpRequest request, FtpData ftpData) { var directoryInfo = Directory.GetParent(ftpData.CurrentDirectory); - if (!directoryInfo.FullName.IsSubPathOf(Path.GetFullPath(RootPath))) + if (!directoryInfo.FullName.IsSubPathOf(Path.GetFullPath(this.RootPath))) { return new FtpResponse { StatusCode = FtpResponseCodes.SyntaxError, Message = "Cannot CDUP from root directory!" }; } diff --git a/MTSC/Common/Ftp/FtpModules/FileModule.cs b/MTSC/Common/Ftp/FtpModules/FileModule.cs index 7abd808..1030402 100644 --- a/MTSC/Common/Ftp/FtpModules/FileModule.cs +++ b/MTSC/Common/Ftp/FtpModules/FileModule.cs @@ -22,13 +22,14 @@ namespace MTSC.Common.Ftp.FtpModules handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.FileStatusOkay, Message = "Opening data connection" }); ftpData.OpenDataConnection(); } - handler.QueueFtpResponse(server, client, StoreFile(request, ftpData)); + + handler.QueueFtpResponse(server, client, this.StoreFile(request, ftpData)); ftpData.CloseDataConnection(); return true; } else if (request.Command == FtpRequestCommands.DELE) { - handler.QueueFtpResponse(server, client, RemoveFile(request, ftpData)); + handler.QueueFtpResponse(server, client, this.RemoveFile(request, ftpData)); return true; } else if (request.Command == FtpRequestCommands.RETR) @@ -38,7 +39,8 @@ namespace MTSC.Common.Ftp.FtpModules handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.FileStatusOkay, Message = "Opening data connection" }); ftpData.OpenDataConnection(); } - handler.QueueFtpResponse(server, client, RetrieveFile(request, ftpData)); + + handler.QueueFtpResponse(server, client, this.RetrieveFile(request, ftpData)); ftpData.CloseDataConnection(); return true; } @@ -62,6 +64,7 @@ namespace MTSC.Common.Ftp.FtpModules fs.Write(bytes, 0, bytes.Length); } } + return new FtpResponse { StatusCode = FtpResponseCodes.ClosingDataConnection, Message = "Transfer complete" }; } else @@ -88,6 +91,7 @@ namespace MTSC.Common.Ftp.FtpModules } } } + return new FtpResponse { StatusCode = FtpResponseCodes.ClosingDataConnection, Message = "Transfer complete" }; } diff --git a/MTSC/Common/Ftp/FtpModules/SystModule.cs b/MTSC/Common/Ftp/FtpModules/SystModule.cs index 3bdc617..a35ba15 100644 --- a/MTSC/Common/Ftp/FtpModules/SystModule.cs +++ b/MTSC/Common/Ftp/FtpModules/SystModule.cs @@ -12,6 +12,7 @@ namespace MTSC.Common.Ftp.FtpModules handler.QueueFtpResponse(server, client, new FtpResponse { StatusCode = FtpResponseCodes.NAMESystemType, Message = "MTSC Webserver " + System.Reflection.Assembly.GetEntryAssembly().GetName().Version }); return true; } + return false; } } diff --git a/MTSC/Common/Ftp/FtpRequest.cs b/MTSC/Common/Ftp/FtpRequest.cs index 3e108f4..d8e5ffe 100644 --- a/MTSC/Common/Ftp/FtpRequest.cs +++ b/MTSC/Common/Ftp/FtpRequest.cs @@ -23,6 +23,7 @@ namespace MTSC.Common.Ftp { throw new UnknownFtpCommandException($"Unknown FTP command: {tokens[0]}"); } + this.Command = command; this.Arguments = tokens.Length > 1 ? tokens.Skip(1).ToArray() : new string[0]; } @@ -34,12 +35,13 @@ namespace MTSC.Common.Ftp public static byte[] ToBytes(FtpRequest request) { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.Append(request.Command.ToString()); - foreach(string argument in request.Arguments) + foreach(var argument in request.Arguments) { sb.Append(' ').Append(argument); } + sb.Append("\r\n"); return Encoding.UTF8.GetBytes(sb.ToString()); } diff --git a/MTSC/Common/Ftp/FtpResponse.cs b/MTSC/Common/Ftp/FtpResponse.cs index 4cfce94..9e110cb 100644 --- a/MTSC/Common/Ftp/FtpResponse.cs +++ b/MTSC/Common/Ftp/FtpResponse.cs @@ -18,16 +18,18 @@ namespace MTSC.Common.Ftp private FtpResponse(byte[] bytes) { - string message = Encoding.UTF8.GetString(bytes); + var message = Encoding.UTF8.GetString(bytes); var tokens = message.Trim('\0').Trim('\n').Trim('\r').Split(' '); if (!int.TryParse(tokens[0], out var statusCodeInt)) { throw new InvalidFtpStatusCodeException($"Expected status code but found [{tokens[0]}]"); } + if (!Enum.IsDefined(typeof(FtpResponseCodes), statusCodeInt)) { throw new InvalidFtpStatusCodeException($"Undefined status code [{statusCodeInt}]"); } + this.StatusCode = (FtpResponseCodes)statusCodeInt; this.Message = tokens.Length > 1 ? tokens.Skip(1).Aggregate((current, next) => current + ' ' + next) : null; } @@ -39,12 +41,13 @@ namespace MTSC.Common.Ftp public static byte[] ToBytes(FtpResponse response) { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.Append((int)response.StatusCode); if (!string.IsNullOrWhiteSpace(response.Message)) { sb.Append(' ').Append(response.Message); } + sb.Append("\r\n"); return Encoding.UTF8.GetBytes(sb.ToString()); } diff --git a/MTSC/Common/Http/Attributes/FromBodyAttribute.cs b/MTSC/Common/Http/Attributes/FromBodyAttribute.cs new file mode 100644 index 0000000..4208a69 --- /dev/null +++ b/MTSC/Common/Http/Attributes/FromBodyAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace MTSC.Common.Http.Attributes +{ + public sealed class FromBodyAttribute : Attribute + { + } +} diff --git a/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs b/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs new file mode 100644 index 0000000..2506559 --- /dev/null +++ b/MTSC/Common/Http/Attributes/FromHeadersAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace MTSC.Common.Http.Attributes +{ + public sealed class FromHeadersAttribute : Attribute + { + public string HeaderName { get; } + + public FromHeadersAttribute(string headerName) + { + this.HeaderName = headerName; + } + } +} diff --git a/MTSC/Common/Http/Attributes/FromUrlAttribute.cs b/MTSC/Common/Http/Attributes/FromUrlAttribute.cs new file mode 100644 index 0000000..ba8a563 --- /dev/null +++ b/MTSC/Common/Http/Attributes/FromUrlAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace MTSC.Common.Http.Attributes +{ + public class FromUrlAttribute : Attribute + { + public string Placeholder { get; } + + public FromUrlAttribute(string placeholder) + { + this.Placeholder = placeholder; + } + } +} diff --git a/MTSC/Common/Http/Cookie.cs b/MTSC/Common/Http/Cookie.cs index 9b2a854..dd216be 100644 --- a/MTSC/Common/Http/Cookie.cs +++ b/MTSC/Common/Http/Cookie.cs @@ -28,7 +28,7 @@ namespace MTSC.Common.Http /// Value of cookie. public Cookie(string key, string value) { - Attributes = new Dictionary(); + this.Attributes = new Dictionary(); this.Key = key; this.Value = value; } @@ -38,14 +38,14 @@ namespace MTSC.Common.Http /// String containing the definition of a cookie public Cookie(string cookieString) { - Attributes = new Dictionary(); - string[] cookieTokens = cookieString.Split(';'); - Key = cookieTokens[0].Split('=')[0].Trim(); - Value = cookieTokens[1].Split('=')[1].Trim(); - for(int i = 1; i < cookieTokens.Length; i++) + this.Attributes = new Dictionary(); + var cookieTokens = cookieString.Split(';'); + this.Key = cookieTokens[0].Split('=')[0].Trim(); + this.Value = cookieTokens[1].Split('=')[1].Trim(); + for(var i = 1; i < cookieTokens.Length; i++) { - string[] attributeTokens = cookieTokens[i].Split('='); - Attributes[attributeTokens[0].Trim()] = attributeTokens.Length > 1 ? attributeTokens[1].Trim() : string.Empty; + var attributeTokens = cookieTokens[i].Split('='); + this.Attributes[attributeTokens[0].Trim()] = attributeTokens.Length > 1 ? attributeTokens[1].Trim() : string.Empty; } } /// @@ -54,11 +54,11 @@ namespace MTSC.Common.Http /// String containing the cookie definition. public string BuildCookieString() { - StringBuilder sb = new StringBuilder(); - sb.Append(Key).Append('=').Append(Value); - if(Attributes.Count > 0) + var sb = new StringBuilder(); + sb.Append(this.Key).Append('=').Append(this.Value); + if(this.Attributes.Count > 0) { - foreach(KeyValuePair attribute in Attributes) + foreach(var attribute in this.Attributes) { sb.Append(';').Append(attribute.Key); if (!string.IsNullOrWhiteSpace(attribute.Value)) @@ -67,6 +67,7 @@ namespace MTSC.Common.Http } } } + return sb.ToString(); } /// @@ -75,7 +76,7 @@ namespace MTSC.Common.Http /// Byte array containin the cookie definition. public byte[] BuildCookieBytes() { - return ASCIIEncoding.ASCII.GetBytes(BuildCookieString()); + return ASCIIEncoding.ASCII.GetBytes(this.BuildCookieString()); } } } diff --git a/MTSC/Common/Http/Forms/Form.cs b/MTSC/Common/Http/Forms/Form.cs index c765cab..ec52bb7 100644 --- a/MTSC/Common/Http/Forms/Form.cs +++ b/MTSC/Common/Http/Forms/Form.cs @@ -5,23 +5,23 @@ namespace MTSC.Common.Http.Forms { public class Form : IEnumerable> { - private Dictionary dictionary = new Dictionary(); + private Dictionary dictionary = new(); - public int Count { get => dictionary.Count; } + public int Count { get => this.dictionary.Count; } public void SetValue(string key, ContentTypeBase value) { - dictionary[key] = value; + this.dictionary[key] = value; } public T GetValue(string key) where T : ContentTypeBase { - return dictionary[key] as T; + return this.dictionary[key] as T; } public bool TryGetValue(string key, out T value) where T : ContentTypeBase { - if (dictionary.TryGetValue(key, out var formValue)) + if (this.dictionary.TryGetValue(key, out var formValue)) { if (formValue.GetType() == typeof(T)) { @@ -36,17 +36,17 @@ namespace MTSC.Common.Http.Forms public ContentTypeBase GetValue(string key) { - return dictionary[key]; + return this.dictionary[key]; } public IEnumerator> GetEnumerator() { - return ((IEnumerable>)dictionary).GetEnumerator(); + return ((IEnumerable>)this.dictionary).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable>)dictionary).GetEnumerator(); + return ((IEnumerable>)this.dictionary).GetEnumerator(); } } } diff --git a/MTSC/Common/Http/HttpMessage.cs b/MTSC/Common/Http/HttpMessage.cs index 1a04819..f8df06b 100644 --- a/MTSC/Common/Http/HttpMessage.cs +++ b/MTSC/Common/Http/HttpMessage.cs @@ -127,7 +127,7 @@ namespace MTSC.Common.Http LastModified = 9 } - private Dictionary headers = new Dictionary(); - private List cookies = new List(); + private Dictionary headers = new(); + private List cookies = new(); } } diff --git a/MTSC/Common/Http/HttpRequest.cs b/MTSC/Common/Http/HttpRequest.cs index 7b815e9..012835c 100644 --- a/MTSC/Common/Http/HttpRequest.cs +++ b/MTSC/Common/Http/HttpRequest.cs @@ -26,19 +26,18 @@ namespace MTSC.Common.Http public string RequestURI { get; set; } public string RequestQuery { get; set; } public byte[] Body { get; set; } = new byte[0]; - public string BodyString { get => Encoding.ASCII.GetString(Body).Trim('\0'); set => Body = Encoding.ASCII.GetBytes(value); } + public string BodyString { get => Encoding.ASCII.GetString(this.Body).Trim('\0'); set => this.Body = Encoding.ASCII.GetBytes(value); } public HttpRequest() { - } public HttpRequest(byte[] requestBytes) { - ParseRequest(requestBytes); + this.ParseRequest(requestBytes); if(this.Method == HttpMethods.Post) { - ParseBodyForm(); + this.ParseBodyForm(); } } @@ -49,21 +48,23 @@ namespace MTSC.Common.Http public byte[] GetPackedRequest() { - return BuildRequest(); + return this.BuildRequest(); } public void AddToBody(byte[] bytesToBeAdded) { - var newBody = new byte[Body.Length + bytesToBeAdded.Length]; - if (Body.Length > 0) + var newBody = new byte[this.Body.Length + bytesToBeAdded.Length]; + if (this.Body.Length > 0) { - Array.Copy(Body, 0, newBody, 0, Body.Length); + Array.Copy(this.Body, 0, newBody, 0, this.Body.Length); } + if (bytesToBeAdded.Length > 0) { - Array.Copy(bytesToBeAdded, 0, newBody, Body.Length, bytesToBeAdded.Length); + Array.Copy(bytesToBeAdded, 0, newBody, this.Body.Length, bytesToBeAdded.Length); } - Body = newBody; + + this.Body = newBody; } private HttpMethods GetMethod(string methodString) @@ -77,16 +78,16 @@ namespace MTSC.Common.Http * 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(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.SP) { - string methodString = parseBuffer.ToString(); - return GetMethod(methodString); + var methodString = parseBuffer.ToString(); + return this.GetMethod(methodString); } else { @@ -99,6 +100,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteMethodException("Incomplete request method. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -108,17 +110,18 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); ms.ReadByte(); //Ignore the first '/' while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.SP) { return parseBuffer.ToString(); } + if (c == '?') { return parseBuffer.ToString(); @@ -134,6 +137,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteRequestURIException("Incomplete request URI. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -143,12 +147,12 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == (byte)HttpHeaders.SP) { return parseBuffer.ToString(); @@ -164,6 +168,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteRequestQueryException("Incomplete request query. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -175,19 +180,20 @@ namespace MTSC.Common.Http * Check if the HTTPVer matches the implementation version. * If not, throw an exception. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1] || c == HttpHeaders.SP) { - string httpVer = parseBuffer.ToString(); + var httpVer = parseBuffer.ToString(); if (httpVer != HttpHeaders.HTTPVER) { throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString()); } + return; } else if (c == HttpHeaders.CRLF[0]) @@ -218,12 +224,12 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a ':' character, parse the header key. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while(ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == ':') { return parseBuffer.ToString(); @@ -239,6 +245,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteHeaderKeyException("Incomplete Header key. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -248,12 +255,12 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while(ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1]) { return parseBuffer.ToString().Trim(); @@ -276,6 +283,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteHeaderValueException("Incomplete header value. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -288,7 +296,7 @@ namespace MTSC.Common.Http { if(this.Method == HttpMethods.Post && this.Form.Count > 0) { - List formData = new List(); + var formData = new List(); string boundary = null; if (this.Headers.ContainsHeader("Content-Type")) { @@ -299,9 +307,10 @@ namespace MTSC.Common.Http boundary = Guid.NewGuid().ToString(); this.Headers["Content-Type"] = "multipart/form-data; boundary=" + boundary; } + formData.AddRange(Encoding.UTF8.GetBytes("--" + boundary + HttpHeaders.CRLF)); - foreach(var content in Form) + foreach(var content in this.Form) { if (content.Value is TextContentType) { formData.AddRange(Encoding.UTF8.GetBytes("Content-Disposition: form-data; name=\"" + content.Key + "\"" + HttpHeaders.CRLF + @@ -315,47 +324,54 @@ namespace MTSC.Common.Http formData.AddRange(((FileContentType)content.Value).Data); formData.AddRange(Encoding.UTF8.GetBytes(HttpHeaders.CRLF)); } + formData.AddRange(Encoding.UTF8.GetBytes("--" + boundary + HttpHeaders.CRLF)); } - var prevBytes = Body; - Body = new byte[prevBytes.Length + formData.Count]; - Array.Copy(formData.ToArray(), 0, Body, 0, formData.Count); - Array.Copy(prevBytes, 0, Body, formData.Count, prevBytes.Length); - } - this.Headers[EntityHeaders.ContentLength] = Body.Length.ToString(); - StringBuilder requestString = new StringBuilder(); - requestString.Append(HttpHeaders.Methods[(int)Method]).Append(HttpHeaders.SP).Append(RequestURI); - if(!string.IsNullOrWhiteSpace(RequestQuery)) + var prevBytes = this.Body; + this.Body = new byte[prevBytes.Length + formData.Count]; + Array.Copy(formData.ToArray(), 0, this.Body, 0, formData.Count); + Array.Copy(prevBytes, 0, this.Body, formData.Count, prevBytes.Length); + } + + this.Headers[EntityHeaders.ContentLength] = this.Body.Length.ToString(); + + var requestString = new StringBuilder(); + requestString.Append(HttpHeaders.Methods[(int)this.Method]).Append(HttpHeaders.SP).Append(this.RequestURI); + if(!string.IsNullOrWhiteSpace(this.RequestQuery)) { - requestString.Append('?').Append(RequestQuery); + requestString.Append('?').Append(this.RequestQuery); } + requestString.Append(HttpHeaders.SP).Append(HttpHeaders.HTTPVER).Append(HttpHeaders.CRLF); - foreach (KeyValuePair header in Headers) + foreach (var header in this.Headers) { requestString.Append(header.Key).Append(':').Append(HttpHeaders.SP).Append(header.Value).Append(HttpHeaders.CRLF); } + requestString.Append(HttpHeaders.CRLF); - if (Cookies.Count > 0) + if (this.Cookies.Count > 0) { requestString.Append(HttpHeaders.RequestCookieHeader).Append(':').Append(HttpHeaders.SP); - for (int i = 0; i < Cookies.Count; i++) + for (var i = 0; i < this.Cookies.Count; i++) { - Cookie cookie = Cookies[i]; + var cookie = this.Cookies[i]; requestString.Append(cookie.BuildCookieString()); - if (i < Cookies.Count - 1) + if (i < this.Cookies.Count - 1) { requestString.Append(';'); } } } - byte[] request = new byte[requestString.Length + (Body == null ? 0 : Body.Length)]; - byte[] requestBytes = ASCIIEncoding.ASCII.GetBytes(requestString.ToString()); + + var request = new byte[requestString.Length + (this.Body == null ? 0 : this.Body.Length)]; + var requestBytes = ASCIIEncoding.ASCII.GetBytes(requestString.ToString()); Array.Copy(requestBytes, 0, request, 0, requestBytes.Length); - if (Body != null) + if (this.Body != null) { - Array.Copy(Body, 0, request, requestBytes.Length, Body.Length); + Array.Copy(this.Body, 0, request, requestBytes.Length, this.Body.Length); } + return request; } /// @@ -367,25 +383,25 @@ namespace MTSC.Common.Http /* * Parse the bytes one by one, respecting the reference manual. */ - MemoryStream ms = new MemoryStream(requestBytes); + var ms = new MemoryStream(requestBytes); /* * 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 - Query, 3 - HTTPVer, 4 - Header, 5 - Value */ - int step = 0; - string headerKey = string.Empty; - string headerValue = string.Empty; + var step = 0; + var headerKey = string.Empty; + var headerValue = string.Empty; while(ms.Position < ms.Length) { if (step == 0) { - Method = ParseMethod(ms); + this.Method = this.ParseMethod(ms); step++; } else if (step == 1) { - RequestURI = ParseRequestURI(ms); + this.RequestURI = this.ParseRequestURI(ms); ms.Seek(-1, SeekOrigin.Current); if (ms.ReadByte() == '?') { @@ -398,17 +414,17 @@ namespace MTSC.Common.Http } else if (step == 2) { - RequestQuery = ParseRequestQuery(ms); + this.RequestQuery = this.ParseRequestQuery(ms); step++; } else if (step == 3) { - ParseHTTPVer(ms); + this.ParseHTTPVer(ms); step++; } else if (step == 4) { - char c = Convert.ToChar(ms.ReadByte()); + var c = Convert.ToChar(ms.ReadByte()); if (c == HttpHeaders.CRLF[0]) { continue; @@ -420,13 +436,13 @@ namespace MTSC.Common.Http else { ms.Seek(-1, SeekOrigin.Current); - headerKey = ParseHeaderKey(ms); + headerKey = this.ParseHeaderKey(ms); step++; } } else if (step == 5) { - char c = Convert.ToChar(ms.ReadByte()); + var c = Convert.ToChar(ms.ReadByte()); if (c == HttpHeaders.CRLF[0]) { continue; @@ -438,27 +454,30 @@ namespace MTSC.Common.Http else { ms.Seek(-1, SeekOrigin.Current); - headerValue = ParseHeaderValue(ms); + headerValue = this.ParseHeaderValue(ms); if (headerKey == HttpHeaders.RequestCookieHeader) { - Cookies.Add(new Cookie(headerValue)); + this.Cookies.Add(new Cookie(headerValue)); } else { - Headers[headerKey] = headerValue; + this.Headers[headerKey] = headerValue; } + step--; } } } + if(step < 4) { throw new IncompleteRequestException($"Incomplete request.", new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } - if (Headers.ContainsHeader(EntityHeaders.ContentLength)) + + if (this.Headers.ContainsHeader(EntityHeaders.ContentLength)) { - int remainingBytes = int.Parse(Headers[EntityHeaders.ContentLength]); + var remainingBytes = int.Parse(this.Headers[EntityHeaders.ContentLength]); if (remainingBytes <= ms.Length - ms.Position) { this.Body = ms.ReadRemainingBytes(); @@ -492,34 +511,35 @@ namespace MTSC.Common.Http /// Dictionary with posted from. public void ParseBodyForm() { - if (Headers.ContainsHeader("Content-Type") && Body != null) + if (this.Headers.ContainsHeader("Content-Type") && this.Body != null) { - if (Headers["Content-Type"] == "application/x-www-form-urlencoded") + if (this.Headers["Content-Type"] == "application/x-www-form-urlencoded") { /* * 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++) + var formKey = string.Empty; + var step = 0; + for (var i = 0; i < this.Body.Length; i++) { if (step == 0) { - formKey = GetField(Body, ref i); + formKey = this.GetField(this.Body, ref i); step++; } else { - Form.SetValue(formKey, new TextContentType("text/plain", GetValue(Body, ref i))); + this.Form.SetValue(formKey, new TextContentType("text/plain", this.GetValue(this.Body, ref i))); step--; } } + return; } - else if (Headers["Content-Type"].Contains("multipart/form-data")) + else if (this.Headers["Content-Type"].Contains("multipart/form-data")) { - GetMultipartForm(); + this.GetMultipartForm(); } else { @@ -533,98 +553,107 @@ namespace MTSC.Common.Http } private Form GetMultipartForm() { - string boundary = this.Headers["Content-Type"].Substring(this.Headers["Content-Type"].IndexOf("=") + 1).Trim('\"'); + var boundary = this.Headers["Content-Type"].Substring(this.Headers["Content-Type"].IndexOf("=") + 1).Trim('\"'); - int bodyIndex = 0; + var bodyIndex = 0; while (true) { - string contentType = "text/plain"; + var contentType = "text/plain"; - if (!MatchesTwoHyphens(bodyIndex)) + if (!this.MatchesTwoHyphens(bodyIndex)) { throw new InvalidPostFormException("No boundary where expected"); } + bodyIndex += 2; - if (!MatchesString(bodyIndex, boundary)) + if (!this.MatchesString(bodyIndex, boundary)) { throw new InvalidPostFormException("No boundary where expected"); } + bodyIndex += boundary.Length; - if (!MatchesCRLF(bodyIndex)) + if (!this.MatchesCRLF(bodyIndex)) { - if (MatchesTwoHyphens(bodyIndex)) + if (this.MatchesTwoHyphens(bodyIndex)) { /* * Reached the end of the multipart message */ break; } + throw new InvalidPostFormException("No new line after boundary"); } + bodyIndex += 2; - if (!MatchesString(bodyIndex, "Content-Disposition: form-data")) + if (!this.MatchesString(bodyIndex, "Content-Disposition: form-data")) { throw new InvalidPostFormException("No Content-Disposition header"); } + bodyIndex += 30; - Dictionary keys = new Dictionary(); - while (Body[bodyIndex] == ';') + var keys = new Dictionary(); + while (this.Body[bodyIndex] == ';') { bodyIndex += 2; - var keyName = GetMultipartKeyName(bodyIndex); + var keyName = this.GetMultipartKeyName(bodyIndex); bodyIndex += keyName.Length + 1; - var keyNameField = GetMultipartKeyNameField(bodyIndex); + var keyNameField = this.GetMultipartKeyNameField(bodyIndex); bodyIndex += keyNameField.Length; - while(Body[bodyIndex] != ';' && Body[bodyIndex] != '\r') + while(this.Body[bodyIndex] != ';' && this.Body[bodyIndex] != '\r') { bodyIndex++; } + keys[keyName] = keyNameField; } - if (!MatchesCRLF(bodyIndex)) + if (!this.MatchesCRLF(bodyIndex)) { throw new InvalidPostFormException("No new line after boundary"); } + bodyIndex += 2; - if (MatchesString(bodyIndex, "Content-Type: ")) + if (this.MatchesString(bodyIndex, "Content-Type: ")) { bodyIndex += 14; - contentType = GetContentType(bodyIndex); + contentType = this.GetContentType(bodyIndex); bodyIndex += contentType.Length + 2; } - if (!MatchesCRLF(bodyIndex)) + if (!this.MatchesCRLF(bodyIndex)) { throw new InvalidPostFormException("No new line after boundary"); } + bodyIndex += 2; - var bytes = GetMultipartValue(bodyIndex, boundary); + var bytes = this.GetMultipartValue(bodyIndex, boundary); if (keys.Keys.Contains("filename")) { - Form.SetValue(keys["name"], new FileContentType(contentType, keys["filename"], bytes)); + this.Form.SetValue(keys["name"], new FileContentType(contentType, keys["filename"], bytes)); } else { - Form.SetValue(keys["name"], new TextContentType(contentType, Encoding.UTF8.GetString(bytes))); + this.Form.SetValue(keys["name"], new TextContentType(contentType, Encoding.UTF8.GetString(bytes))); } + bodyIndex += bytes.Length + 2; } - return Form; + return this.Form; } private bool MatchesCRLF(int index) { - if (index + 1 < Body.Length) + if (index + 1 < this.Body.Length) { - if (Body[index] == HttpHeaders.CRLF[0] && Body[index + 1] == HttpHeaders.CRLF[1]) + if (this.Body[index] == HttpHeaders.CRLF[0] && this.Body[index + 1] == HttpHeaders.CRLF[1]) { return true; } @@ -640,9 +669,9 @@ namespace MTSC.Common.Http } private bool MatchesTwoHyphens(int index) { - if (index + 1 < Body.Length) + if (index + 1 < this.Body.Length) { - if (Body[index] == '-' && Body[index + 1] == '-') + if (this.Body[index] == '-' && this.Body[index + 1] == '-') { return true; } @@ -658,11 +687,11 @@ namespace MTSC.Common.Http } private bool MatchesString(int index, string s) { - for (int i = 0; i < s.Length; i++) + for (var i = 0; i < s.Length; i++) { - if (index + i < Body.Length) + if (index + i < this.Body.Length) { - if ((char)Body[index + i] != s[i]) + if ((char)this.Body[index + i] != s[i]) { return false; } @@ -672,58 +701,64 @@ namespace MTSC.Common.Http return false; } } + return true; } private string GetMultipartKeyName(int index) { - StringBuilder sb = new StringBuilder(); - while (Body[index] != '=') + var sb = new StringBuilder(); + while (this.Body[index] != '=') { - sb.Append((char)Body[index]); + sb.Append((char)this.Body[index]); index++; } + return sb.ToString(); } private string GetMultipartKeyNameField(int index) { - StringBuilder sb = new StringBuilder(); - while (Body[index] != ';' && Body[index] != '\n' && Body[index] != '\r') + var sb = new StringBuilder(); + while (this.Body[index] != ';' && this.Body[index] != '\n' && this.Body[index] != '\r') { - sb.Append((char)Body[index]); + sb.Append((char)this.Body[index]); index++; } + return sb.ToString().Trim('\"'); } private byte[] GetMultipartValue(int bodyIndex, string boundary) { - int startIndex = bodyIndex; - bool gatheringData = true; + var startIndex = bodyIndex; + var gatheringData = true; while (true) { - if (Body[bodyIndex] == '-') + if (this.Body[bodyIndex] == '-') { /* * Possible boundary detected. Try and see if it matches. */ - if (Body[bodyIndex + 1] == '-' && MatchesString(bodyIndex + 2, boundary)) + if (this.Body[bodyIndex + 1] == '-' && this.MatchesString(bodyIndex + 2, boundary)) { break; } } + bodyIndex++; } - byte[] newBytes = new byte[bodyIndex - startIndex - 2]; - Array.Copy(Body, startIndex, newBytes, 0, bodyIndex - startIndex - 2); + + var newBytes = new byte[bodyIndex - startIndex - 2]; + Array.Copy(this.Body, startIndex, newBytes, 0, bodyIndex - startIndex - 2); return newBytes; } private string GetContentType(int bodyIndex) { - StringBuilder sb = new StringBuilder(); - while (!MatchesCRLF(bodyIndex)) + var sb = new StringBuilder(); + while (!this.MatchesCRLF(bodyIndex)) { - sb.Append((char)Body[bodyIndex]); + sb.Append((char)this.Body[bodyIndex]); bodyIndex++; } + return sb.ToString(); } private string GetField(byte[] buffer, ref int index) @@ -731,7 +766,7 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); for (; index < buffer.Length; index++) { try @@ -750,6 +785,7 @@ namespace MTSC.Common.Http 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) @@ -757,7 +793,7 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); for (; index < buffer.Length; index++) { try @@ -776,6 +812,7 @@ namespace MTSC.Common.Http throw new InvalidPostFormException("Invalid form field. Buffer: " + parseBuffer.ToString(), e); } } + return parseBuffer.ToString().Trim(); } } diff --git a/MTSC/Common/Http/HttpRequestHeaderDictionary.cs b/MTSC/Common/Http/HttpRequestHeaderDictionary.cs index 3185a44..0eddc5c 100644 --- a/MTSC/Common/Http/HttpRequestHeaderDictionary.cs +++ b/MTSC/Common/Http/HttpRequestHeaderDictionary.cs @@ -11,10 +11,10 @@ namespace MTSC.Common.Http { private Dictionary headers { get; } = new Dictionary(); - public string this[string key] { get => headers[key]; set => headers[key] = value; } - public string this[GeneralHeaders key] { get => headers[HttpHeaders.GeneralHeaders[(int)key]]; set => headers[HttpHeaders.GeneralHeaders[(int)key]] = value; } - public string this[EntityHeaders key] { get => headers[HttpHeaders.EntityHeaders[(int)key]]; set => headers[HttpHeaders.EntityHeaders[(int)key]] = value; } - public string this[RequestHeaders key] { get => headers[HttpHeaders.RequestHeaders[(int)key]]; set => headers[HttpHeaders.RequestHeaders[(int)key]] = value; } + public string this[string key] { get => this.headers[key]; set => this.headers[key] = value; } + public string this[GeneralHeaders key] { get => this.headers[HttpHeaders.GeneralHeaders[(int)key]]; set => this.headers[HttpHeaders.GeneralHeaders[(int)key]] = value; } + public string this[EntityHeaders key] { get => this.headers[HttpHeaders.EntityHeaders[(int)key]]; set => this.headers[HttpHeaders.EntityHeaders[(int)key]] = value; } + public string this[RequestHeaders key] { get => this.headers[HttpHeaders.RequestHeaders[(int)key]]; set => this.headers[HttpHeaders.RequestHeaders[(int)key]] = value; } /// /// Check if the message contains a header. @@ -23,7 +23,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(RequestHeaders header) { - return ContainsHeader(HttpHeaders.RequestHeaders[(int)header]); + return this.ContainsHeader(HttpHeaders.RequestHeaders[(int)header]); } /// /// Check if the message contains a header. @@ -32,7 +32,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(GeneralHeaders header) { - return ContainsHeader(HttpHeaders.GeneralHeaders[(int)header]); + return this.ContainsHeader(HttpHeaders.GeneralHeaders[(int)header]); } /// /// Check if the message contains a header. @@ -41,7 +41,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(EntityHeaders header) { - return ContainsHeader(HttpHeaders.EntityHeaders[(int)header]); + return this.ContainsHeader(HttpHeaders.EntityHeaders[(int)header]); } /// /// Check if the message contains a header. @@ -50,7 +50,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(string header) { - return headers.ContainsKey(header); + return this.headers.ContainsKey(header); } /// /// Adds header with specified value. @@ -99,12 +99,12 @@ namespace MTSC.Common.Http public IEnumerator> GetEnumerator() { - return ((IEnumerable>)headers).GetEnumerator(); + return ((IEnumerable>)this.headers).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable>)headers).GetEnumerator(); + return ((IEnumerable>)this.headers).GetEnumerator(); } } } diff --git a/MTSC/Common/Http/HttpResponse.cs b/MTSC/Common/Http/HttpResponse.cs index ae4160f..0418508 100644 --- a/MTSC/Common/Http/HttpResponse.cs +++ b/MTSC/Common/Http/HttpResponse.cs @@ -14,7 +14,7 @@ namespace MTSC.Common.Http /// public List Cookies { get; } = new List(); public byte[] Body { get; set; } = new byte[0]; - public string BodyString { get => Encoding.ASCII.GetString(Body); set => Body = Encoding.ASCII.GetBytes(value); } + public string BodyString { get => Encoding.ASCII.GetString(this.Body); set => this.Body = Encoding.ASCII.GetBytes(value); } public StatusCodes StatusCode { get; set; } public string StatusString { get; set; } = string.Empty; public HttpResponseHeaderDictionary Headers { get; } = new HttpResponseHeaderDictionary(); @@ -26,7 +26,7 @@ namespace MTSC.Common.Http public HttpResponse(byte[] responseBytes) { - ParseResponse(responseBytes); + this.ParseResponse(responseBytes); } public static HttpResponse FromBytes(byte[] responseBytes) @@ -36,7 +36,7 @@ namespace MTSC.Common.Http public byte[] GetPackedResponse(bool includeContentLengthHeader) { - return BuildResponse(includeContentLengthHeader); + return this.BuildResponse(includeContentLengthHeader); } /// @@ -54,28 +54,32 @@ namespace MTSC.Common.Http * If there is a body, include the size of the body. If there is no body, * set the value of the content length to 0. */ - Headers[EntityHeaders.ContentLength] = Body == null ? "0" : Body.Length.ToString(); + this.Headers[EntityHeaders.ContentLength] = this.Body == null ? "0" : this.Body.Length.ToString(); } - StringBuilder responseString = new StringBuilder(); + + var responseString = new StringBuilder(); responseString.Append(HttpHeaders.HTTPVER).Append(HttpHeaders.SP) .Append((int)this.StatusCode).Append(HttpHeaders.SP) .Append(this.StatusString != string.Empty ? this.StatusString : this.StatusCode.ToString()).Append(HttpHeaders.CRLF); - foreach (KeyValuePair header in Headers) + foreach (var header in this.Headers) { responseString.Append(header.Key).Append(':').Append(HttpHeaders.SP).Append(header.Value).Append(HttpHeaders.CRLF); } - foreach (Cookie cookie in Cookies) + + foreach (var cookie in this.Cookies) { responseString.Append(HttpHeaders.ResponseCookieHeader).Append(':').Append(HttpHeaders.SP).Append(cookie.BuildCookieString()).Append(HttpHeaders.CRLF); } + responseString.Append(HttpHeaders.CRLF); - byte[] response = new byte[responseString.Length + (Body == null ? 0 : Body.Length)]; - byte[] responseBytes = Encoding.ASCII.GetBytes(responseString.ToString()); + var response = new byte[responseString.Length + (this.Body == null ? 0 : this.Body.Length)]; + var responseBytes = Encoding.ASCII.GetBytes(responseString.ToString()); Array.Copy(responseBytes, 0, response, 0, responseBytes.Length); - if (Body != null) + if (this.Body != null) { - Array.Copy(Body, 0, response, responseBytes.Length, Body.Length); + Array.Copy(this.Body, 0, response, responseBytes.Length, this.Body.Length); } + return response; } /// @@ -87,15 +91,15 @@ namespace MTSC.Common.Http /* * Parse the bytes one by one, respecting the reference manual. */ - MemoryStream ms = new MemoryStream(responseBytes); + var ms = new MemoryStream(responseBytes); /* * 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; + var step = 0; + var headerKey = string.Empty; + var headerValue = string.Empty; while (ms.Position < ms.Length) { if (step == 0) @@ -115,7 +119,7 @@ namespace MTSC.Common.Http } else if (step == 3) { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[0]) { continue; @@ -127,13 +131,13 @@ namespace MTSC.Common.Http else { ms.Seek(-1, SeekOrigin.Current); - headerKey = ParseHeaderKey(ms); + headerKey = this.ParseHeaderKey(ms); step++; } } else if (step == 4) { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[0]) { continue; @@ -145,19 +149,21 @@ namespace MTSC.Common.Http else { ms.Seek(-1, SeekOrigin.Current); - headerValue = ParseHeaderValue(ms); + headerValue = this.ParseHeaderValue(ms); if (headerKey == HttpHeaders.ResponseCookieHeader) { - Cookies.Add(new Cookie(headerValue)); + this.Cookies.Add(new Cookie(headerValue)); } else { - Headers[headerKey] = headerValue; + this.Headers[headerKey] = headerValue; } + step--; } } } + if (ms.Length - ms.Position > 1) { /* @@ -166,6 +172,7 @@ namespace MTSC.Common.Http */ this.Body = ms.ReadRemainingBytes(); } + return; } private void ParseHTTPVer(MemoryStream ms) @@ -175,19 +182,20 @@ namespace MTSC.Common.Http * Check if the HTTPVer matches the implementation version. * If not, throw an exception. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while(ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1] || c == HttpHeaders.SP) { - string httpVer = parseBuffer.ToString(); + var httpVer = parseBuffer.ToString(); if (httpVer != HttpHeaders.HTTPVER) { throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString()); } + return; } else if (c == HttpHeaders.CRLF[0]) @@ -215,10 +223,10 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a ':' character, parse the header key. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while(ms.Position < ms.Length) { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); try { if (c == ':') @@ -235,6 +243,7 @@ namespace MTSC.Common.Http throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString(), e); } } + throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString()); } @@ -243,10 +252,10 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); try { if (c == HttpHeaders.CRLF[1]) @@ -270,6 +279,7 @@ namespace MTSC.Common.Http throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString(), e); } } + throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString()); } @@ -278,10 +288,10 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a SP character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); try { if (c == HttpHeaders.SP) @@ -298,6 +308,7 @@ namespace MTSC.Common.Http throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString(), e); } } + throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString()); } @@ -306,10 +317,10 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); try { if (c == HttpHeaders.CRLF[1]) @@ -333,6 +344,7 @@ namespace MTSC.Common.Http throw new InvalidStatusCodeException("Invalid status code. Buffer: " + parseBuffer.ToString(), e); } } + throw new InvalidHeaderException("Invalid status code. Buffer: " + parseBuffer.ToString()); } } diff --git a/MTSC/Common/Http/HttpResponseHeaderDictionary.cs b/MTSC/Common/Http/HttpResponseHeaderDictionary.cs index 79158cf..1d79ded 100644 --- a/MTSC/Common/Http/HttpResponseHeaderDictionary.cs +++ b/MTSC/Common/Http/HttpResponseHeaderDictionary.cs @@ -8,10 +8,10 @@ namespace MTSC.Common.Http { private Dictionary headers { get; } = new Dictionary(); - public string this[string key] { get => headers[key]; set => headers[key] = value; } - public string this[GeneralHeaders key] { get => headers[HttpHeaders.GeneralHeaders[(int)key]]; set => headers[HttpHeaders.GeneralHeaders[(int)key]] = value; } - public string this[EntityHeaders key] { get => headers[HttpHeaders.EntityHeaders[(int)key]]; set => headers[HttpHeaders.EntityHeaders[(int)key]] = value; } - public string this[ResponseHeaders key] { get => headers[HttpHeaders.ResponseHeaders[(int)key]]; set => headers[HttpHeaders.ResponseHeaders[(int)key]] = value; } + public string this[string key] { get => this.headers[key]; set => this.headers[key] = value; } + public string this[GeneralHeaders key] { get => this.headers[HttpHeaders.GeneralHeaders[(int)key]]; set => this.headers[HttpHeaders.GeneralHeaders[(int)key]] = value; } + public string this[EntityHeaders key] { get => this.headers[HttpHeaders.EntityHeaders[(int)key]]; set => this.headers[HttpHeaders.EntityHeaders[(int)key]] = value; } + public string this[ResponseHeaders key] { get => this.headers[HttpHeaders.ResponseHeaders[(int)key]]; set => this.headers[HttpHeaders.ResponseHeaders[(int)key]] = value; } /// /// Check if the message contains a header. @@ -20,7 +20,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(ResponseHeaders header) { - return ContainsHeader(HttpHeaders.ResponseHeaders[(int)header]); + return this.ContainsHeader(HttpHeaders.ResponseHeaders[(int)header]); } /// /// Check if the message contains a header. @@ -29,7 +29,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(GeneralHeaders header) { - return ContainsHeader(HttpHeaders.GeneralHeaders[(int)header]); + return this.ContainsHeader(HttpHeaders.GeneralHeaders[(int)header]); } /// /// Check if the message contains a header. @@ -38,7 +38,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(EntityHeaders header) { - return ContainsHeader(HttpHeaders.EntityHeaders[(int)header]); + return this.ContainsHeader(HttpHeaders.EntityHeaders[(int)header]); } /// /// Check if the message contains a header. @@ -47,7 +47,7 @@ namespace MTSC.Common.Http /// True if the message contains a header with the provided key. public bool ContainsHeader(string header) { - return headers.ContainsKey(header); + return this.headers.ContainsKey(header); } /// /// Adds header with specified value @@ -96,12 +96,12 @@ namespace MTSC.Common.Http public IEnumerator> GetEnumerator() { - return ((IEnumerable>)headers).GetEnumerator(); + return ((IEnumerable>)this.headers).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable>)headers).GetEnumerator(); + return ((IEnumerable>)this.headers).GetEnumerator(); } } } diff --git a/MTSC/Common/Http/PartialHttpRequest.cs b/MTSC/Common/Http/PartialHttpRequest.cs index e17d505..f3ff2c5 100644 --- a/MTSC/Common/Http/PartialHttpRequest.cs +++ b/MTSC/Common/Http/PartialHttpRequest.cs @@ -8,7 +8,7 @@ using static MTSC.Common.Http.HttpMessage; namespace MTSC.Common.Http { - class PartialHttpRequest + internal class PartialHttpRequest { public HttpRequestHeaderDictionary Headers { get; } = new HttpRequestHeaderDictionary(); @@ -23,16 +23,15 @@ namespace MTSC.Common.Http public string RequestURI { get; set; } public string RequestQuery { get; set; } public byte[] Body { get; set; } = new byte[0]; - public string BodyString { get => ASCIIEncoding.ASCII.GetString(Body).Trim('\0'); set => Body = ASCIIEncoding.ASCII.GetBytes(value); } + public string BodyString { get => ASCIIEncoding.ASCII.GetString(this.Body).Trim('\0'); set => this.Body = ASCIIEncoding.ASCII.GetBytes(value); } public PartialHttpRequest() { - } public PartialHttpRequest(byte[] requestBytes) { - ParseRequest(requestBytes); + this.ParseRequest(requestBytes); } public static PartialHttpRequest FromBytes(byte[] requestBytes) @@ -42,11 +41,12 @@ namespace MTSC.Common.Http public HttpRequest ToRequest() { - HttpRequest httpRequest = new HttpRequest(); + var httpRequest = new HttpRequest(); foreach(var header in this.Headers) { httpRequest.Headers[header.Key] = header.Value; } + httpRequest.Method = this.Method; httpRequest.RequestQuery = this.RequestQuery; httpRequest.RequestURI = this.RequestURI; @@ -55,23 +55,26 @@ namespace MTSC.Common.Http { httpRequest.Cookies.Add(cookie); } + httpRequest.ParseBodyForm(); return httpRequest; } public void AddToBody(byte[] bytesToBeAdded) { - var newBody = new byte[Body.Length + bytesToBeAdded.Length]; - if (Body.Length > 0) + var newBody = new byte[this.Body.Length + bytesToBeAdded.Length]; + if (this.Body.Length > 0) { - Array.Copy(Body, 0, newBody, 0, Body.Length); + Array.Copy(this.Body, 0, newBody, 0, this.Body.Length); } + if (bytesToBeAdded.Length > 0) { - Array.Copy(bytesToBeAdded, 0, newBody, Body.Length, bytesToBeAdded.Length); + Array.Copy(bytesToBeAdded, 0, newBody, this.Body.Length, bytesToBeAdded.Length); } - Body = newBody; - if (this.Headers.ContainsHeader(EntityHeaders.ContentLength) && int.Parse(Headers[EntityHeaders.ContentLength]) == Body.Length) + + this.Body = newBody; + if (this.Headers.ContainsHeader(EntityHeaders.ContentLength) && int.Parse(this.Headers[EntityHeaders.ContentLength]) == this.Body.Length) { this.Complete = true; } @@ -88,16 +91,16 @@ namespace MTSC.Common.Http * 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(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.SP) { - string methodString = parseBuffer.ToString(); - return GetMethod(methodString); + var methodString = parseBuffer.ToString(); + return this.GetMethod(methodString); } else { @@ -110,6 +113,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteMethodException("Incomplete request method. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -119,17 +123,18 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); ms.ReadByte(); //Ignore the first '/' while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.SP) { return parseBuffer.ToString(); } + if (c == '?') { return parseBuffer.ToString(); @@ -145,6 +150,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteRequestURIException("Incomplete request URI. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -154,12 +160,12 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == (byte)HttpHeaders.SP) { return parseBuffer.ToString(); @@ -175,6 +181,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteRequestQueryException("Incomplete request query. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -186,19 +193,20 @@ namespace MTSC.Common.Http * Check if the HTTPVer matches the implementation version. * If not, throw an exception. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1] || c == HttpHeaders.SP) { - string httpVer = parseBuffer.ToString(); + var httpVer = parseBuffer.ToString(); if (httpVer != HttpHeaders.HTTPVER) { throw new InvalidHttpVersionException("Invalid HTTP version. Buffer: " + parseBuffer.ToString()); } + return; } else if (c == HttpHeaders.CRLF[0]) @@ -229,12 +237,12 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a ':' character, parse the header key. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == ':') { return parseBuffer.ToString(); @@ -250,6 +258,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteHeaderKeyException("Incomplete Header key. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -259,12 +268,12 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); while (ms.Position < ms.Length) { try { - char c = (char)ms.ReadByte(); + var c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1]) { return parseBuffer.ToString().Trim(); @@ -287,6 +296,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + throw new IncompleteHeaderValueException("Incomplete header value. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -300,25 +310,25 @@ namespace MTSC.Common.Http /* * Parse the bytes one by one, respecting the reference manual. */ - MemoryStream ms = new MemoryStream(requestBytes); + var ms = new MemoryStream(requestBytes); /* * 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 - Query, 3 - HTTPVer, 4 - Header, 5 - Value */ - int step = 0; - string headerKey = string.Empty; - string headerValue = string.Empty; + var step = 0; + var headerKey = string.Empty; + var headerValue = string.Empty; while (ms.Position < ms.Length) { if (step == 0) { - Method = ParseMethod(ms); + this.Method = this.ParseMethod(ms); step++; } else if (step == 1) { - RequestURI = ParseRequestURI(ms); + this.RequestURI = this.ParseRequestURI(ms); ms.Seek(-1, SeekOrigin.Current); if (ms.ReadByte() == '?') { @@ -331,17 +341,17 @@ namespace MTSC.Common.Http } else if (step == 2) { - RequestQuery = ParseRequestQuery(ms); + this.RequestQuery = this.ParseRequestQuery(ms); step++; } else if (step == 3) { - ParseHTTPVer(ms); + this.ParseHTTPVer(ms); step++; } else if (step == 4) { - char c = Convert.ToChar(ms.ReadByte()); + var c = Convert.ToChar(ms.ReadByte()); if (c == HttpHeaders.CRLF[0]) { continue; @@ -353,13 +363,13 @@ namespace MTSC.Common.Http else { ms.Seek(-1, SeekOrigin.Current); - headerKey = ParseHeaderKey(ms); + headerKey = this.ParseHeaderKey(ms); step++; } } else if (step == 5) { - char c = Convert.ToChar(ms.ReadByte()); + var c = Convert.ToChar(ms.ReadByte()); if (c == HttpHeaders.CRLF[0]) { continue; @@ -371,28 +381,31 @@ namespace MTSC.Common.Http else { ms.Seek(-1, SeekOrigin.Current); - headerValue = ParseHeaderValue(ms); + headerValue = this.ParseHeaderValue(ms); if (headerKey == HttpHeaders.RequestCookieHeader) { - Cookies.Add(new Cookie(headerValue)); + this.Cookies.Add(new Cookie(headerValue)); } else { - Headers[headerKey] = headerValue; + this.Headers[headerKey] = headerValue; } + step--; } } } + if (step < 4) { throw new IncompleteRequestException($"Incomplete request.", new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } + this.HeaderByteCount = (int)ms.Position; - if (Headers.ContainsHeader(EntityHeaders.ContentLength)) + if (this.Headers.ContainsHeader(EntityHeaders.ContentLength)) { - int remainingBytes = int.Parse(Headers[EntityHeaders.ContentLength]); + var remainingBytes = int.Parse(this.Headers[EntityHeaders.ContentLength]); if (remainingBytes <= ms.Length - ms.Position) { this.Body = ms.ReadRemainingBytes(); @@ -413,7 +426,8 @@ namespace MTSC.Common.Http this.Body = ms.ReadRemainingBytes(); } } - Complete = true; + + this.Complete = true; return; } private string GetField(byte[] buffer, ref int index) @@ -421,7 +435,7 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); for (; index < buffer.Length; index++) { try @@ -440,6 +454,7 @@ namespace MTSC.Common.Http 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) @@ -447,7 +462,7 @@ namespace MTSC.Common.Http /* * Get each character one by one. When meeting a LF character, parse the value. */ - StringBuilder parseBuffer = new StringBuilder(); + var parseBuffer = new StringBuilder(); for (; index < buffer.Length; index++) { try @@ -466,6 +481,7 @@ namespace MTSC.Common.Http throw new InvalidPostFormException("Invalid form field. Buffer: " + parseBuffer.ToString(), e); } } + return parseBuffer.ToString().Trim(); } } diff --git a/MTSC/Common/Http/RoutingModules/Http200Module.cs b/MTSC/Common/Http/RoutingModules/Http200Module.cs index 6d56be1..1654659 100644 --- a/MTSC/Common/Http/RoutingModules/Http200Module.cs +++ b/MTSC/Common/Http/RoutingModules/Http200Module.cs @@ -14,10 +14,10 @@ namespace MTSC.Common.Http.RoutingModules public override Task HandleRequest(HttpRequest request) { - return Task.FromResult(OK); + return Task.FromResult(this.OK); } - private HttpResponse OK => new HttpResponse() + private HttpResponse OK => new() { StatusCode = HttpMessage.StatusCodes.OK }; diff --git a/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs b/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs index 2d3b55e..baf6670 100644 --- a/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs +++ b/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs @@ -1,6 +1,7 @@ using MTSC.ServerSide; using MTSC.ServerSide.Handlers; using System; +using System.ComponentModel; using System.Linq; using System.Threading.Tasks; @@ -52,129 +53,23 @@ namespace MTSC.Common.Http.RoutingModules } public abstract class HttpRouteBase : HttpRouteBase { - private readonly static object cachedLock = new object(); - private static IRequestConverter CachedConverter { get; set; } - public sealed override Task HandleRequest(HttpRequest request) { - lock (cachedLock) - { - if (CachedConverter is null) - { - CachedConverter = ImplementConverter(); - } - } - - return this.HandleRequest(CachedConverter.ConvertHttpRequest(request)); + var typeConverter = TypeDescriptor.GetConverter(typeof(T)); + return this.HandleRequest((T)typeConverter.ConvertFrom(request)); } public abstract Task HandleRequest(T request); - - private static bool MatchesRequiredType(RequestConvertAttribute attribute) - { - if (attribute.ConverterType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequestConverter))) - { - return false; - } - - return true; - } - - private static IRequestConverter ImplementConverter() - { - var converterType = typeof(T) - .GetCustomAttributes(true) - .OfType() - .Where(MatchesRequiredType) - .Select(attribute => attribute.ConverterType) - .FirstOrDefault(); - if (converterType is null) - { - throw new InvalidOperationException($"No converter found for type {typeof(T).FullName}"); - } - - var converter = Activator.CreateInstance(converterType) as IRequestConverter; - return converter; - } } public abstract class HttpRouteBase : HttpRouteBase { - private static readonly object reqLock = new object(), respLock = new object(); - private static IRequestConverter CachedRequestConverter { get; set; } - private static IResponseConverter CachedResponseConverter { get; set; } - public sealed async override Task HandleRequest(HttpRequest request) { - lock (reqLock) - { - if (CachedRequestConverter is null) - { - CachedRequestConverter = ImplementRequestConverter(); - } - } - - lock (respLock) - { - if (CachedResponseConverter is null) - { - CachedResponseConverter = ImplementResponseConverter(); - } - } - - return CachedResponseConverter.ConvertResponse(await this.HandleRequest(CachedRequestConverter.ConvertHttpRequest(request))); + var requestTypeConverter = TypeDescriptor.GetConverter(typeof(TReceive)); + var responseTypeConverter = TypeDescriptor.GetConverter(typeof(TSend)); + return (HttpResponse)responseTypeConverter.ConvertTo(await this.HandleRequest((TReceive)requestTypeConverter.ConvertFrom(request)), typeof(HttpResponse)); } public abstract Task HandleRequest(TReceive request); - - private static bool MatchesRequiredRequestType(RequestConvertAttribute attribute) - { - if (attribute.ConverterType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequestConverter))) - { - return false; - } - - return true; - } - private static bool MatchesRequiredResponseType(ResponseConvertAttribute attribute) - { - if (attribute.ConverterType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IResponseConverter))) - { - return false; - } - - return true; - } - private static IResponseConverter ImplementResponseConverter() - { - var converterType = typeof(TSend) - .GetCustomAttributes(true) - .OfType() - .Where(MatchesRequiredResponseType) - .Select(attribute => attribute.ConverterType) - .FirstOrDefault(); - if (converterType is null) - { - throw new InvalidOperationException($"No converter found for type {typeof(TSend).FullName}"); - } - - var converter = Activator.CreateInstance(converterType) as IResponseConverter; - return converter; - } - private static IRequestConverter ImplementRequestConverter() - { - var converterType = typeof(TReceive) - .GetCustomAttributes(true) - .OfType() - .Where(MatchesRequiredRequestType) - .Select(attribute => attribute.ConverterType) - .FirstOrDefault(); - if (converterType is null) - { - throw new InvalidOperationException($"No converter found for type {typeof(TReceive).FullName}"); - } - - var converter = Activator.CreateInstance(converterType) as IRequestConverter; - return converter; - } } } diff --git a/MTSC/Common/Http/RoutingModules/IRequestConverter.cs b/MTSC/Common/Http/RoutingModules/IRequestConverter.cs deleted file mode 100644 index 61fa505..0000000 --- a/MTSC/Common/Http/RoutingModules/IRequestConverter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MTSC.Common.Http.RoutingModules -{ - public interface IRequestConverter - { - T ConvertHttpRequest(HttpRequest httpRequest); - } -} diff --git a/MTSC/Common/Http/RoutingModules/IResponseConverter.cs b/MTSC/Common/Http/RoutingModules/IResponseConverter.cs deleted file mode 100644 index 2efb414..0000000 --- a/MTSC/Common/Http/RoutingModules/IResponseConverter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MTSC.Common.Http.RoutingModules -{ - public interface IResponseConverter - { - HttpResponse ConvertResponse(T response); - } -} diff --git a/MTSC/Common/Http/RoutingModules/RequestConvertAttribute.cs b/MTSC/Common/Http/RoutingModules/RequestConvertAttribute.cs deleted file mode 100644 index 93ccd41..0000000 --- a/MTSC/Common/Http/RoutingModules/RequestConvertAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Linq; - -namespace MTSC.Common.Http.RoutingModules -{ - [AttributeUsage(AttributeTargets.Class)] - public class RequestConvertAttribute : Attribute - { - public Type ConverterType { get; } - - public RequestConvertAttribute(Type type) - { - if (!type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequestConverter<>))) - { - throw new InvalidOperationException($"{type.FullName} is not a {typeof(IRequestConverter<>).FullName}"); - } - - this.ConverterType = type; - } - } -} diff --git a/MTSC/Common/Http/RoutingModules/ResponseConverterAttribute.cs b/MTSC/Common/Http/RoutingModules/ResponseConverterAttribute.cs deleted file mode 100644 index af1b195..0000000 --- a/MTSC/Common/Http/RoutingModules/ResponseConverterAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Linq; - -namespace MTSC.Common.Http.RoutingModules -{ - [AttributeUsage(AttributeTargets.Class)] - public class ResponseConvertAttribute : Attribute - { - public Type ConverterType { get; } - - public ResponseConvertAttribute(Type type) - { - if (!type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IResponseConverter<>))) - { - throw new InvalidOperationException($"{type.FullName} is not a {typeof(IResponseConverter<>).FullName}"); - } - - this.ConverterType = type; - } - } -} diff --git a/MTSC/Common/Http/ServerModules/FileServerModule.cs b/MTSC/Common/Http/ServerModules/FileServerModule.cs index 6e346cf..d5e5479 100644 --- a/MTSC/Common/Http/ServerModules/FileServerModule.cs +++ b/MTSC/Common/Http/ServerModules/FileServerModule.cs @@ -10,8 +10,8 @@ namespace MTSC.Common.Http.ServerModules public sealed class FileServerModule : IHttpModule { private string rootFolder; - ConcurrentDictionary> fileCache = new ConcurrentDictionary>(); - List toRemoveCache = new List(); + ConcurrentDictionary> fileCache = new(); + List toRemoveCache = new(); public FileServerModule(string rootFolder = "src") { this.rootFolder = Path.GetFullPath(rootFolder); @@ -25,16 +25,17 @@ namespace MTSC.Common.Http.ServerModules { request.RequestURI = "/index.html"; } - string requestFile = this.rootFolder + request.RequestURI; + + var requestFile = this.rootFolder + request.RequestURI; if(requestFile.IsSubPathOf(this.rootFolder) && File.Exists(requestFile)) { /* * If file is in cache, retrieve it from cache. */ byte[] bodyData; - if (fileCache.ContainsKey(requestFile)) + if (this.fileCache.ContainsKey(requestFile)) { - bodyData = fileCache[requestFile].Item1; + bodyData = this.fileCache[requestFile].Item1; } else { @@ -43,18 +44,19 @@ namespace MTSC.Common.Http.ServerModules /* * Insert or update cache with the requested file. */ - Tuple tuple = new Tuple(bodyData, DateTime.Now); - fileCache.AddOrUpdate(requestFile, tuple, (key, oldValue) => oldValue = tuple); + var tuple = new Tuple(bodyData, DateTime.Now); + this.fileCache.AddOrUpdate(requestFile, tuple, (key, oldValue) => oldValue = tuple); /* * Build the response packet. */ response.Body = bodyData; response.StatusCode = HttpMessage.StatusCodes.OK; response.Headers[HttpMessage.EntityHeaders.ContentType] = "text/html"; - if (!fileCache.ContainsKey(requestFile)) + if (!this.fileCache.ContainsKey(requestFile)) { server.LogDebug("Adding " + requestFile + " to server cache."); } + return true; } else @@ -63,25 +65,28 @@ namespace MTSC.Common.Http.ServerModules response.StatusCode = HttpMessage.StatusCodes.NotFound; } } + return false; } void IHttpModule.Tick(ServerSide.Server server, HttpHandler handler) { - foreach(KeyValuePair> keyValuePair in fileCache) + foreach(var keyValuePair in this.fileCache) { if((DateTime.Now - keyValuePair.Value.Item2).TotalSeconds > 60) { - toRemoveCache.Add(keyValuePair.Key); + this.toRemoveCache.Add(keyValuePair.Key); } } - foreach(string fileName in toRemoveCache) + + foreach(var fileName in this.toRemoveCache) { Tuple tp; - while(!fileCache.TryRemove(fileName, out tp)) { }; + while(!this.fileCache.TryRemove(fileName, out tp)) { }; server.LogDebug("Removed " + fileName + " from cache."); } - toRemoveCache.Clear(); + + this.toRemoveCache.Clear(); } #endregion diff --git a/MTSC/Common/Http/ServerModules/HelloWorldModule.cs b/MTSC/Common/Http/ServerModules/HelloWorldModule.cs index 86090e9..1e1aa91 100644 --- a/MTSC/Common/Http/ServerModules/HelloWorldModule.cs +++ b/MTSC/Common/Http/ServerModules/HelloWorldModule.cs @@ -19,6 +19,7 @@ namespace MTSC.Common.Http.ServerModules response.Headers["Server"] = "MTSC"; response.Body = this.response; } + return true; } diff --git a/MTSC/Common/Http/ServerModules/Http404Module.cs b/MTSC/Common/Http/ServerModules/Http404Module.cs index f7e40f2..6469831 100644 --- a/MTSC/Common/Http/ServerModules/Http404Module.cs +++ b/MTSC/Common/Http/ServerModules/Http404Module.cs @@ -25,6 +25,7 @@ namespace MTSC.Common.Http.ServerModules response.StatusCode = HttpMessage.StatusCodes.NotFound; response.Headers[HttpMessage.GeneralHeaders.Date] = DateTime.Now.ToString(); } + return true; } diff --git a/MTSC/Common/Http/ServerModules/PostModule.cs b/MTSC/Common/Http/ServerModules/PostModule.cs index 452695d..09f3ab9 100644 --- a/MTSC/Common/Http/ServerModules/PostModule.cs +++ b/MTSC/Common/Http/ServerModules/PostModule.cs @@ -23,16 +23,16 @@ namespace MTSC.Common.Http.ServerModules } else { - Dictionary formData = new Dictionary(); - int length = int.Parse(request.Headers[HttpMessage.EntityHeaders.ContentLength]); + var formData = new Dictionary(); + var length = int.Parse(request.Headers[HttpMessage.EntityHeaders.ContentLength]); int step = 0, parsedLength = 0; - StringBuilder fieldBuilder = new StringBuilder(); - StringBuilder valueBuilder = new StringBuilder(); + var fieldBuilder = new StringBuilder(); + var valueBuilder = new StringBuilder(); /* * Parse request body to obtain the form data. * Example of a form: field1=value1&field2=value2 */ - for (int i = 0; i < request.Body.Length; i++) + for (var i = 0; i < request.Body.Length; i++) { if (request.Body[i] == '\n') { @@ -70,6 +70,7 @@ namespace MTSC.Common.Http.ServerModules } } } + return formData; } } @@ -88,14 +89,14 @@ namespace MTSC.Common.Http.ServerModules { if (request.Headers[HttpMessage.EntityHeaders.ContentType].Contains(urlEncodedHeader)) { - Dictionary form = ParseFormUrlEncoded(request); + var form = this.ParseFormUrlEncoded(request); FormReceived?.Invoke(this, form); server.LogDebug("Received POST form of " + form.Keys.Count + " keys!"); return true; } else if (request.Headers[HttpMessage.EntityHeaders.ContentType].Contains(multipartHeader)) { - Dictionary form = ParseFormMultipart(request); + var form = this.ParseFormMultipart(request); FormReceived?.Invoke(this, form); server.LogDebug("Received POST form of " + form.Keys.Count + " keys!"); return true; diff --git a/MTSC/Common/ProducerConsumerQueue.cs b/MTSC/Common/ProducerConsumerQueue.cs index 56b92b4..e72c3ea 100644 --- a/MTSC/Common/ProducerConsumerQueue.cs +++ b/MTSC/Common/ProducerConsumerQueue.cs @@ -5,24 +5,30 @@ namespace MTSC.Common { public class ProducerConsumerQueue : IProducerQueue, IConsumerQueue { - private Queue queue = new Queue(); + private Queue queue = new(); TValue IConsumerQueue.Dequeue() { - lock (queue) + lock (this.queue) { - if (queue.Count > 0) return queue.Dequeue(); - else throw new InvalidOperationException("There are no elements to dequeue from the queue"); + if (this.queue.Count > 0) + { + return this.queue.Dequeue(); + } + else + { + throw new InvalidOperationException("There are no elements to dequeue from the queue"); + } } } bool IConsumerQueue.TryDequeue(out TValue value) { - lock (queue) + lock (this.queue) { - if (queue.Count > 0) + if (this.queue.Count > 0) { - value = queue.Dequeue(); + value = this.queue.Dequeue(); return true; } else @@ -35,9 +41,9 @@ namespace MTSC.Common void IProducerQueue.Enqueue(TValue value) { - lock (queue) + lock (this.queue) { - queue.Enqueue(value); + this.queue.Enqueue(value); } } } diff --git a/MTSC/Common/TimeoutSuppressedStream.cs b/MTSC/Common/TimeoutSuppressedStream.cs index 07e226b..4ad4190 100644 --- a/MTSC/Common/TimeoutSuppressedStream.cs +++ b/MTSC/Common/TimeoutSuppressedStream.cs @@ -31,6 +31,7 @@ namespace MTSC.Common // this is a "Stream.Read() returning 0 means that no data is available" return 0; } + throw; } } diff --git a/MTSC/Common/UrlExtensions.cs b/MTSC/Common/UrlExtensions.cs deleted file mode 100644 index 7df26f3..0000000 --- a/MTSC/Common/UrlExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.Text; - -namespace MTSC.Common -{ - static class UrlExtensions - { - public static IReadOnlyList ExtractUrlPlaceholders(string url) - { - var list = new List(); - bool begin = false; - StringBuilder tokenBuilder = new StringBuilder(); - var index = 0; - for (int i = 0; i < url.Length; i++) - { - var c = url[i]; - if (c == '{') - { - begin = true; - tokenBuilder.Clear(); - continue; - } - else if (c == '}') - { - begin = false; - list.Add(new UrlPlaceholder { Index = index, Placeholder = tokenBuilder.ToString(), ContinuationChar = i + 1 < url.Length ? url[i + 1] : (char)0 }); - continue; - } - if (begin) - { - tokenBuilder.Append(c); - } - else - { - index++; - } - } - return list; - } - - public static IReadOnlyList ExtractValuesFromPlaceholders(string url, IReadOnlyList placeholders) - { - var list = new List(); - if (placeholders.Count == 0) - { - return list; - } - StringBuilder valueBuilder = new StringBuilder(); - var placeHolderIndex = 0; - var otherCharacterIndex = 0; - for (int i = 0; i < url.Length; i++) - { - while (otherCharacterIndex < placeholders[placeHolderIndex].Index) - { - i++; - otherCharacterIndex++; - } - while (i < url.Length && url[i] != placeholders[placeHolderIndex].ContinuationChar) - { - var c = url[i]; - valueBuilder.Append(c); - i++; - } - list.Add(new UrlValue { Placeholder = placeholders[placeHolderIndex].Placeholder, Value = valueBuilder.ToString() }); - placeHolderIndex++; - valueBuilder.Clear(); - } - return list; - } - } -} diff --git a/MTSC/Common/UrlPlaceholder.cs b/MTSC/Common/UrlPlaceholder.cs deleted file mode 100644 index 7e79807..0000000 --- a/MTSC/Common/UrlPlaceholder.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MTSC.Common -{ - class UrlPlaceholder - { - public int Index { get; set; } - public char ContinuationChar { get; set; } - public string Placeholder { get; set; } - } -} diff --git a/MTSC/Common/UrlValue.cs b/MTSC/Common/UrlValue.cs index f9784f1..fb756f8 100644 --- a/MTSC/Common/UrlValue.cs +++ b/MTSC/Common/UrlValue.cs @@ -1,6 +1,6 @@ namespace MTSC.Common { - class UrlValue + internal class UrlValue { public string Placeholder { get; set; } public string Value { get; set; } diff --git a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs index 2430694..864779c 100644 --- a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs +++ b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs @@ -6,11 +6,11 @@ namespace MTSC.Common.WebSockets.ClientModules { public sealed class ChatModule : IWebsocketModule { - static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + static RNGCryptoServiceProvider rng = new(); #region Public Methods public void SendMessage(WebsocketHandler websocketHandler, string message) { - WebsocketMessage websocketMessage = new WebsocketMessage(); + var websocketMessage = new WebsocketMessage(); websocketMessage.Data = Encoding.UTF8.GetBytes(message); websocketMessage.Opcode = WebsocketMessage.Opcodes.Text; websocketMessage.FIN = true; @@ -22,7 +22,7 @@ namespace MTSC.Common.WebSockets.ClientModules #region Interface Implementation bool IWebsocketModule.HandleReceivedMessage(Client.Client client, WebsocketHandler handler, WebsocketMessage receivedMessage) { - string messageString = Encoding.UTF8.GetString(receivedMessage.Data); + var messageString = Encoding.UTF8.GetString(receivedMessage.Data); client.Log(">" + messageString); return false; } diff --git a/MTSC/Common/WebSockets/RoutingModules/WebsocketRouteBase.cs b/MTSC/Common/WebSockets/RoutingModules/WebsocketRouteBase.cs index 230c9f7..fdf0eda 100644 --- a/MTSC/Common/WebSockets/RoutingModules/WebsocketRouteBase.cs +++ b/MTSC/Common/WebSockets/RoutingModules/WebsocketRouteBase.cs @@ -14,7 +14,7 @@ namespace MTSC.Common.WebSockets.RoutingModules public void CallConnectionInitialized() { - ConnectionInitialized(); + this.ConnectionInitialized(); } public void CallHandleReceivedMessage(WebsocketMessage receivedMessage) { @@ -77,7 +77,7 @@ namespace MTSC.Common.WebSockets.RoutingModules } public abstract class WebsocketRouteBase : WebsocketRouteBase { - private readonly static object cachedLock = new object(); + private readonly static object cachedLock = new(); private static IWebsocketMessageConverter CachedConverter { get; set; } public sealed override void HandleReceivedMessage(WebsocketMessage receivedMessage) @@ -131,7 +131,7 @@ namespace MTSC.Common.WebSockets.RoutingModules } public abstract class WebsocketRouteBase : WebsocketRouteBase { - private readonly static object recLock = new object(), sendLock = new object(); + private readonly static object recLock = new(), sendLock = new(); private static IWebsocketMessageConverter CachedReceiveConverter { get; set; } private static IWebsocketMessageConverter CachedSendConverter { get; set; } diff --git a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs index 1b78686..6ce6105 100644 --- a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs +++ b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs @@ -14,7 +14,7 @@ namespace MTSC.Common.WebSockets.ServerModules #region Interface Implementation bool IWebsocketModule.HandleReceivedMessage(ServerSide.Server server, WebsocketHandler handler, ClientData client, WebsocketMessage receivedMessage) { - SendMessage(handler, client, receivedMessage); + this.SendMessage(handler, client, receivedMessage); return false; } diff --git a/MTSC/Common/WebSockets/WebsocketMessage.cs b/MTSC/Common/WebSockets/WebsocketMessage.cs index 3334f04..2749d22 100644 --- a/MTSC/Common/WebSockets/WebsocketMessage.cs +++ b/MTSC/Common/WebSockets/WebsocketMessage.cs @@ -17,23 +17,23 @@ namespace MTSC.Common.WebSockets Pong = 10 } - byte controlByte = new byte(); + byte controlByte = new(); byte[] lengthBytes; byte[] data; /// /// FIN bit. /// - public bool FIN { get => (controlByte & 0x80) == 0x80; set => controlByte = (byte)(value? controlByte | 0x80 : controlByte & 0x7F);} + public bool FIN { get => (this.controlByte & 0x80) == 0x80; set => this.controlByte = (byte)(value? this.controlByte | 0x80 : this.controlByte & 0x7F);} /// /// Frame Opcode /// /// Gets and sets the 4 lower bits of the first byte. - public Opcodes Opcode { get => (Opcodes)(controlByte & 0xF); - set => controlByte = (byte)((controlByte & 0xF0) | ((int)value & 0xF)); } + public Opcodes Opcode { get => (Opcodes)(this.controlByte & 0xF); + set => this.controlByte = (byte)((this.controlByte & 0xF0) | ((int)value & 0xF)); } /// /// Mask bit. /// - public bool Masked { get => (lengthBytes[0] & 0x80) == 0x80; set => lengthBytes[0] = (byte)(value ? lengthBytes[0] | 0x80 : lengthBytes[0] & 0x7F); } + public bool Masked { get => (this.lengthBytes[0] & 0x80) == 0x80; set => this.lengthBytes[0] = (byte)(value ? this.lengthBytes[0] | 0x80 : this.lengthBytes[0] & 0x7F); } /// /// Length of message. /// @@ -41,18 +41,18 @@ namespace MTSC.Common.WebSockets { get { - if ((lengthBytes[0] & 0x7F) <= 125) + if ((this.lengthBytes[0] & 0x7F) <= 125) { - return (ulong)(lengthBytes[0] & 0x7F); + return (ulong)(this.lengthBytes[0] & 0x7F); } - else if ((lengthBytes[0] & 0x7F) == 126) + else if ((this.lengthBytes[0] & 0x7F) == 126) { - return (ulong)((lengthBytes[1] << 8) + lengthBytes[2]); + return (ulong)((this.lengthBytes[1] << 8) + this.lengthBytes[2]); } - else if ((lengthBytes[0] & 0x7F) == 127) + else if ((this.lengthBytes[0] & 0x7F) == 127) { - return (ulong)((lengthBytes[1] << 56) + (lengthBytes[2] << 48) + (lengthBytes[3] << 40) + (lengthBytes[4] << 32) + - (lengthBytes[5] << 24) + (lengthBytes[6] << 16) + (lengthBytes[7] << 8) + lengthBytes[8]); + return (ulong)((this.lengthBytes[1] << 56) + (this.lengthBytes[2] << 48) + (this.lengthBytes[3] << 40) + (this.lengthBytes[4] << 32) + + (this.lengthBytes[5] << 24) + (this.lengthBytes[6] << 16) + (this.lengthBytes[7] << 8) + this.lengthBytes[8]); } else { @@ -63,41 +63,44 @@ namespace MTSC.Common.WebSockets { if(value <= 125) { - if(lengthBytes.Length != 1) + if(this.lengthBytes.Length != 1) { - byte[] newLengthBytes = new byte[1]; - newLengthBytes[0] = lengthBytes[0]; - lengthBytes = newLengthBytes; + var newLengthBytes = new byte[1]; + newLengthBytes[0] = this.lengthBytes[0]; + this.lengthBytes = newLengthBytes; } - lengthBytes[0] = (byte)((lengthBytes[0] & 0x80) | ((byte)value & 0x7F)); + + this.lengthBytes[0] = (byte)((this.lengthBytes[0] & 0x80) | ((byte)value & 0x7F)); } else if(value <= UInt16.MaxValue) { - if (lengthBytes.Length != 3) + if (this.lengthBytes.Length != 3) { - byte[] newLengthBytes = new byte[3]; - newLengthBytes[0] = (byte)(lengthBytes[0] & 0x80); + var newLengthBytes = new byte[3]; + newLengthBytes[0] = (byte)(this.lengthBytes[0] & 0x80); newLengthBytes[0] += 126; - lengthBytes = newLengthBytes; + this.lengthBytes = newLengthBytes; } - for(int i = 1; i < 3; i++) + + for(var i = 1; i < 3; i++) { - lengthBytes[3 - i] = (byte)(value & 0xFF); + this.lengthBytes[3 - i] = (byte)(value & 0xFF); value >>= 8; } } else if(value <= UInt64.MaxValue) { - if (lengthBytes.Length != 8) + if (this.lengthBytes.Length != 8) { - byte[] newLengthBytes = new byte[9]; - newLengthBytes[0] = (byte)(lengthBytes[0] & 0x80); + var newLengthBytes = new byte[9]; + newLengthBytes[0] = (byte)(this.lengthBytes[0] & 0x80); newLengthBytes[0] += 127; - lengthBytes = newLengthBytes; + this.lengthBytes = newLengthBytes; } - for (int i = 1; i < 9; i++) + + for (var i = 1; i < 9; i++) { - lengthBytes[9 - i] = (byte)(value & 0xFF); + this.lengthBytes[9 - i] = (byte)(value & 0xFF); value >>= 8; } } @@ -116,11 +119,11 @@ namespace MTSC.Common.WebSockets /// public byte[] Data { - get => data; + get => this.data; set { - MessageLength = (ulong)value.Length; - data = value; + this.MessageLength = (ulong)value.Length; + this.data = value; } } @@ -130,22 +133,22 @@ namespace MTSC.Common.WebSockets /// Byte array containing the message bytes. public WebsocketMessage(byte[] messageBytes) { - controlByte = messageBytes[0]; - Mask = new byte[4]; + this.controlByte = messageBytes[0]; + this.Mask = new byte[4]; ulong dataLength = 0; - int dataIndex = 0; + var dataIndex = 0; if ((messageBytes[1] & 0x7F) <= 125) { - lengthBytes = new byte[1]; - lengthBytes[0] = messageBytes[1]; + this.lengthBytes = new byte[1]; + this.lengthBytes[0] = messageBytes[1]; dataLength = (ulong)(messageBytes[1] & 0x7F); dataIndex = 2; } else if ((messageBytes[1] & 0x7F) == 126) { dataLength = (ulong)((messageBytes[2] << 8) + messageBytes[3]); - lengthBytes = new byte[3]; - Array.Copy(messageBytes, 1, lengthBytes, 0, 3); + this.lengthBytes = new byte[3]; + Array.Copy(messageBytes, 1, this.lengthBytes, 0, 3); dataIndex = 4; } else if ((messageBytes[1] & 0x7F) == 127) @@ -153,23 +156,25 @@ namespace MTSC.Common.WebSockets dataLength = (ulong)((messageBytes[3] << 56) + (messageBytes[3] << 48) + (messageBytes[4] << 40) + (messageBytes[5] << 32) + (messageBytes[6] << 24) + (messageBytes[7] << 16) + (messageBytes[8] << 8) + messageBytes[9]); dataIndex = 10; - lengthBytes = new byte[9]; - Array.Copy(messageBytes, 1, lengthBytes, 0, 9); + this.lengthBytes = new byte[9]; + Array.Copy(messageBytes, 1, this.lengthBytes, 0, 9); } else { - lengthBytes = new byte[0]; + this.lengthBytes = new byte[0]; throw new InvalidWebsocketFormatException("Length formatting is wrong"); } + if ((messageBytes[1] & 0x80) > 0) { - Array.Copy(messageBytes, dataIndex, Mask, 0, 4); + Array.Copy(messageBytes, dataIndex, this.Mask, 0, 4); dataIndex += 4; } - data = new byte[dataLength]; + + this.data = new byte[dataLength]; for(ulong i = 0; i < dataLength; i++) { - data[i] = (byte)(messageBytes[(ulong)dataIndex + i] ^ Mask[i % 4]); + this.data[i] = (byte)(messageBytes[(ulong)dataIndex + i] ^ this.Mask[i % 4]); } } /// @@ -177,10 +182,10 @@ namespace MTSC.Common.WebSockets /// public WebsocketMessage() { - controlByte = new byte(); - Mask = new byte[4]; - data = new byte[0]; - lengthBytes = new byte[1]; + this.controlByte = new byte(); + this.Mask = new byte[4]; + this.data = new byte[0]; + this.lengthBytes = new byte[1]; } /// /// Get the packed message bytes. @@ -188,21 +193,24 @@ namespace MTSC.Common.WebSockets /// An array containing the message. public byte[] GetMessageBytes() { - if(data == null) + if(this.data == null) { throw new NoDataException("There is no data in the message"); } - byte[] messageBytes = new byte[1 + lengthBytes.Length + (Masked ? 4 : 0) + data.Length]; - messageBytes[0] = controlByte; - Array.Copy(lengthBytes, 0, messageBytes, 1, lengthBytes.Length); - if (Masked) + + var messageBytes = new byte[1 + this.lengthBytes.Length + (this.Masked ? 4 : 0) + this.data.Length]; + messageBytes[0] = this.controlByte; + Array.Copy(this.lengthBytes, 0, messageBytes, 1, this.lengthBytes.Length); + if (this.Masked) { - Array.Copy(Mask, 0, messageBytes, 1 + lengthBytes.Length, Mask.Length); + Array.Copy(this.Mask, 0, messageBytes, 1 + this.lengthBytes.Length, this.Mask.Length); } - for(int i = 0; i < data.Length; i++) + + for(var i = 0; i < this.data.Length; i++) { - messageBytes[1 + lengthBytes.Length + (Masked ? 4 : 0) + i] = (byte)(Masked ? data[i] ^ Mask[i % 4] : data[i]); + messageBytes[1 + this.lengthBytes.Length + (this.Masked ? 4 : 0) + i] = (byte)(this.Masked ? this.data[i] ^ this.Mask[i % 4] : this.data[i]); } + return messageBytes; } } diff --git a/MTSC/CommunicationPrimitives.cs b/MTSC/CommunicationPrimitives.cs index 4b0b5e5..ca5573e 100644 --- a/MTSC/CommunicationPrimitives.cs +++ b/MTSC/CommunicationPrimitives.cs @@ -27,6 +27,7 @@ namespace MTSC { stream = safeStream; } + var buffer = new byte[1024]; var ms = new MemoryStream(); stream.ReadTimeout = 1; @@ -74,13 +75,14 @@ namespace MTSC { stream = client.GetStream(); } + stream.Write(message.MessageBytes, 0, (int)message.MessageLength); stream.Flush(); } public static Message BuildMessage(byte[] msgData) { - Message message = new Message((uint)msgData.Length, msgData); + var message = new Message((uint)msgData.Length, msgData); return message; } } diff --git a/MTSC/HelperFunctions.cs b/MTSC/HelperFunctions.cs index ba31ce3..a5ca4ea 100644 --- a/MTSC/HelperFunctions.cs +++ b/MTSC/HelperFunctions.cs @@ -2,21 +2,27 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using System.Xml; +[assembly: InternalsVisibleTo("MTSC.UnitTests")] namespace MTSC { - static class HelperFunctions + internal static class HelperFunctions { + private const string Prefix = "<"; + private const string Suffix = ">k__BackingField"; + #region XML public static void FromXmlString(this RSA rsa, string xmlString) { - RSAParameters parameters = new RSAParameters(); + var parameters = new RSAParameters(); - XmlDocument xmlDoc = new XmlDocument(); + var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlString); if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue")) @@ -46,7 +52,7 @@ namespace MTSC public static string ToXmlString(this RSA rsa, bool includePrivateParameters) { - RSAParameters parameters = rsa.ExportParameters(includePrivateParameters); + var parameters = rsa.ExportParameters(includePrivateParameters); return string.Format("{0}{1}

{2}

{3}{4}{5}{6}{7}
", parameters.Modulus != null ? Convert.ToBase64String(parameters.Modulus) : null, @@ -67,10 +73,10 @@ namespace MTSC ///
public static bool IsSubPathOf(this string path, string baseDirPath) { - string normalizedPath = Path.GetFullPath(path.Replace('/', '\\') + var normalizedPath = Path.GetFullPath(path.Replace('/', '\\') .WithEnding("\\")); - string normalizedBaseDirPath = Path.GetFullPath(baseDirPath.Replace('/', '\\') + var normalizedBaseDirPath = Path.GetFullPath(baseDirPath.Replace('/', '\\') .WithEnding("\\")); return normalizedPath.StartsWith(normalizedBaseDirPath, StringComparison.OrdinalIgnoreCase); @@ -84,18 +90,22 @@ namespace MTSC public static string WithEnding(this string str, string ending) { if (str == null) + { return ending; + } - string result = str; + var result = str; // Right() is 1-indexed, so include these cases // * Append no characters // * Append up to N characters, where N is ending length - for (int i = 0; i <= ending.Length; i++) + for (var i = 0; i <= ending.Length; i++) { - string tmp = result + ending.Right(i); + var tmp = result + ending.Right(i); if (tmp.EndsWith(ending)) + { return tmp; + } } return result; @@ -111,6 +121,7 @@ namespace MTSC { throw new ArgumentNullException("value"); } + if (length < 0) { throw new ArgumentOutOfRangeException("length", length, "Length is less than zero"); @@ -121,18 +132,23 @@ namespace MTSC public static string RelativePath(this string absPath, string relTo) { - string[] absDirs = absPath.Split('\\'); - string[] relDirs = relTo.Split('\\'); + var absDirs = absPath.Split('\\'); + var relDirs = relTo.Split('\\'); // Get the shortest of the two paths - int len = absDirs.Length < relDirs.Length ? absDirs.Length : relDirs.Length; + var len = absDirs.Length < relDirs.Length ? absDirs.Length : relDirs.Length; // Use to determine where in the loop we exited - int lastCommonRoot = -1; int index; + var lastCommonRoot = -1; int index; // Find common root for (index = 0; index < len; index++) { if (absDirs[index] == relDirs[index]) + { lastCommonRoot = index; - else break; + } + else + { + break; + } } // If we didn't find a common prefix then throw if (lastCommonRoot == -1) @@ -140,32 +156,36 @@ namespace MTSC throw new ArgumentException("Paths do not have a common base"); } // Build up the relative path - StringBuilder relativePath = new StringBuilder(); + var relativePath = new StringBuilder(); // Add on the .. for (index = lastCommonRoot + 1; index < absDirs.Length; index++) { - if (absDirs[index].Length > 0) relativePath.Append("..\\"); + if (absDirs[index].Length > 0) + { + relativePath.Append("..\\"); + } } // Add on the folders for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++) { relativePath.Append(relDirs[index] + "\\"); } + relativePath.Append(relDirs[relDirs.Length - 1]); return relativePath.ToString(); } public static byte[] ReadRemainingBytes(this MemoryStream ms) { - byte[] buffer = new byte[ms.Length - ms.Position]; + var buffer = new byte[ms.Length - ms.Position]; ms.Read(buffer, 0, buffer.Length); return buffer; } public static byte[] TrimTrailingNullBytes(this byte[] bytes) { - int trimSize = 0; - for(int i = bytes.Length - 1; i >= 0; i--) + var trimSize = 0; + for(var i = bytes.Length - 1; i >= 0; i--) { if(bytes[i] == 0) { @@ -176,9 +196,40 @@ namespace MTSC break; } } - byte[] newBytes = new byte[bytes.Length - trimSize]; + + var newBytes = new byte[bytes.Length - trimSize]; Array.Copy(bytes, 0, newBytes, 0, newBytes.Length); return newBytes; } + + /// + /// Returns the backing field of a property. + /// Source: https://gist.github.com/NickStrupat/39e659e53a7aa000b737 + /// + /// + /// + public static FieldInfo GetBackingField(this PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } + + if (!propertyInfo.CanRead || !propertyInfo.GetGetMethod(nonPublic: true).IsDefined(typeof(CompilerGeneratedAttribute), inherit: true)) + { + return null; + } + + var backingFieldName = GetBackingFieldName(propertyInfo.Name); + var backingField = propertyInfo.DeclaringType?.GetField(backingFieldName, BindingFlags.Instance | BindingFlags.NonPublic); + if (backingField == null) + { + return null; + } + + return !backingField.IsDefined(typeof(CompilerGeneratedAttribute), inherit: true) ? null : backingField; + } + + private static string GetBackingFieldName(string propertyName) => $"{Prefix}{propertyName}{Suffix}"; } } diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index d664945..1ad70f2 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -5,13 +5,13 @@ netcoreapp2.1;net48;netstandard2.0;netcoreapp3.1;net5.0 - 3.3 + 4.0 latest Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 3.3.0.0 - 3.3.0.0 + 4.0.0.0 + 4.0.0.0 true AnyCPU;x64 https://github.com/AlexMacocian/MTSC @@ -27,6 +27,7 @@ + diff --git a/MTSC/ServerSide/ClientData.cs b/MTSC/ServerSide/ClientData.cs index 07a4d31..0981fcf 100644 --- a/MTSC/ServerSide/ClientData.cs +++ b/MTSC/ServerSide/ClientData.cs @@ -11,7 +11,7 @@ namespace MTSC.ServerSide ///
public class ClientData : IDisposable, IActiveClient, IQueueHolder { - private readonly ProducerConsumerQueue messageQueue = new ProducerConsumerQueue(); + private readonly ProducerConsumerQueue messageQueue = new(); public TcpClient TcpClient { get; } /// @@ -43,7 +43,7 @@ namespace MTSC.ServerSide /// public ResourceDictionary Resources { get; set; } = new ResourceDictionary(); - IConsumerQueue IQueueHolder.ConsumerQueue => messageQueue; + IConsumerQueue IQueueHolder.ConsumerQueue => this.messageQueue; bool IActiveClient.ReadingData { get; set; } public ClientData(TcpClient client) @@ -81,28 +81,28 @@ namespace MTSC.ServerSide #region IActiveClient Implementation void IActiveClient.UpdateLastReceivedMessage() { - LastActivityTime = LastReceivedMessageTime = DateTime.Now; + this.LastActivityTime = this.LastReceivedMessageTime = DateTime.Now; } void IActiveClient.UpdateLastActivity() { - LastActivityTime = DateTime.Now; + this.LastActivityTime = DateTime.Now; } #endregion #region IQueueHolder Imeplementation void IQueueHolder.Enqueue(Message value) { - (messageQueue as IProducerQueue).Enqueue(value); + (this.messageQueue as IProducerQueue).Enqueue(value); } Message IQueueHolder.Dequeue() { - return (messageQueue as IConsumerQueue).Dequeue(); + return (this.messageQueue as IConsumerQueue).Dequeue(); } bool IQueueHolder.TryDequeue(out Message Value) { - return (messageQueue as IConsumerQueue).TryDequeue(out Value); + return (this.messageQueue as IConsumerQueue).TryDequeue(out Value); } #endregion #region IDisposable Support @@ -110,18 +110,18 @@ namespace MTSC.ServerSide protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!this.disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). } - TcpClient?.Dispose(); - SslStream?.Dispose(); - Resources?.Dispose(); + this.TcpClient?.Dispose(); + this.SslStream?.Dispose(); + this.Resources?.Dispose(); - disposedValue = true; + this.disposedValue = true; } } @@ -136,7 +136,7 @@ namespace MTSC.ServerSide public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); + this.Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } diff --git a/MTSC/ServerSide/Handlers/BroadcastHandler.cs b/MTSC/ServerSide/Handlers/BroadcastHandler.cs index 257054f..cafb8eb 100644 --- a/MTSC/ServerSide/Handlers/BroadcastHandler.cs +++ b/MTSC/ServerSide/Handlers/BroadcastHandler.cs @@ -26,10 +26,11 @@ namespace MTSC.ServerSide.Handlers { server.LogDebug("Broadcast: " + UnicodeEncoding.Unicode.GetString(message.MessageBytes)); server.LogDebug("From: " + client.TcpClient.Client.RemoteEndPoint.ToString()); - foreach(ClientData clientStruct in server.Clients) + foreach(var clientStruct in server.Clients) { server.QueueMessage(clientStruct, message.MessageBytes); } + return false; } diff --git a/MTSC/ServerSide/Handlers/EncryptionHandler.cs b/MTSC/ServerSide/Handlers/EncryptionHandler.cs index 64c0991..2370f9a 100644 --- a/MTSC/ServerSide/Handlers/EncryptionHandler.cs +++ b/MTSC/ServerSide/Handlers/EncryptionHandler.cs @@ -37,8 +37,8 @@ namespace MTSC.ServerSide.Handlers public EncryptionHandler(RSACryptoServiceProvider rsa) { this.rsa = rsa; - privateKey = HelperFunctions.ToXmlString(rsa, true); - publicKey = HelperFunctions.ToXmlString(rsa, false); + this.privateKey = HelperFunctions.ToXmlString(rsa, true); + this.publicKey = HelperFunctions.ToXmlString(rsa, false); } #endregion #region Public Methods @@ -51,11 +51,11 @@ namespace MTSC.ServerSide.Handlers // Set your salt here, change it to meet your flavor: // The salt bytes must be at least 8 bytes. - byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + var saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { - using (RijndaelManaged AES = new RijndaelManaged()) + using (var AES = new RijndaelManaged()) { AES.KeySize = 256; AES.BlockSize = 128; @@ -73,6 +73,7 @@ namespace MTSC.ServerSide.Handlers cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length); cs.Close(); } + encryptedBytes = ms.ToArray(); } } @@ -86,11 +87,11 @@ namespace MTSC.ServerSide.Handlers // Set your salt here, change it to meet your flavor: // The salt bytes must be at least 8 bytes. - byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + var saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { - using (RijndaelManaged AES = new RijndaelManaged()) + using (var AES = new RijndaelManaged()) { AES.KeySize = 256; AES.BlockSize = 128; @@ -108,6 +109,7 @@ namespace MTSC.ServerSide.Handlers cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length); cs.Close(); } + decryptedBytes = ms.ToArray(); } } @@ -145,18 +147,18 @@ namespace MTSC.ServerSide.Handlers var additionalData = client.Resources.GetResource(); if (additionalData.ClientState == ClientState.Initial || additionalData.ClientState == ClientState.Negotiating) { - string asciiMessage = ASCIIEncoding.ASCII.GetString(message.MessageBytes); + var asciiMessage = ASCIIEncoding.ASCII.GetString(message.MessageBytes); if (asciiMessage == CommunicationPrimitives.RequestPublicKey) { - server.QueueMessage(client, ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.SendPublicKey + ":" + publicKey)); + server.QueueMessage(client, ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.SendPublicKey + ":" + this.publicKey)); additionalData.ClientState = ClientState.Negotiating; return true; } else if (asciiMessage.Contains(CommunicationPrimitives.SendEncryptionKey)) { - byte[] encryptedKey = new byte[message.MessageLength - CommunicationPrimitives.SendEncryptionKey.Length - 1]; + var encryptedKey = new byte[message.MessageLength - CommunicationPrimitives.SendEncryptionKey.Length - 1]; Array.Copy(message.MessageBytes, CommunicationPrimitives.SendEncryptionKey.Length + 1, encryptedKey, 0, encryptedKey.Length); - byte[] decryptedKey = rsa.Decrypt(encryptedKey, false); + var decryptedKey = this.rsa.Decrypt(encryptedKey, false); additionalData.Key = decryptedKey; server.QueueMessage(client, ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.AcceptEncryptionKey)); additionalData.ClientState = ClientState.Encrypted; @@ -186,8 +188,8 @@ namespace MTSC.ServerSide.Handlers /* * Decrypt message before returning. */ - byte[] encryptedBytes = message.MessageBytes; - byte[] decryptedBytes = DecryptBytes(additionalData.Key, encryptedBytes); + var encryptedBytes = message.MessageBytes; + var decryptedBytes = this.DecryptBytes(additionalData.Key, encryptedBytes); message = new Message((uint)decryptedBytes.Length, decryptedBytes); return false; } @@ -217,8 +219,9 @@ namespace MTSC.ServerSide.Handlers var additionalData = client.Resources.GetResource(); if (additionalData.ClientState == ClientState.Encrypted) { - message = CommunicationPrimitives.BuildMessage(EncryptBytes(additionalData.Key, message.MessageBytes)); + message = CommunicationPrimitives.BuildMessage(this.EncryptBytes(additionalData.Key, message.MessageBytes)); } + return false; } #endregion diff --git a/MTSC/ServerSide/Handlers/FtpHandler.cs b/MTSC/ServerSide/Handlers/FtpHandler.cs index 3352b28..bf95327 100644 --- a/MTSC/ServerSide/Handlers/FtpHandler.cs +++ b/MTSC/ServerSide/Handlers/FtpHandler.cs @@ -22,7 +22,7 @@ namespace MTSC.ServerSide.Handlers Initialized } - private List ftpModules = new List(); + private List ftpModules = new(); public TimeSpan ReadyDelay { get; set; } = TimeSpan.FromSeconds(1); @@ -34,7 +34,7 @@ namespace MTSC.ServerSide.Handlers public FtpHandler AddModule(IFtpModule module) { - ftpModules.Add(module); + this.ftpModules.Add(module); return this; } @@ -52,12 +52,12 @@ namespace MTSC.ServerSide.Handlers { client.Resources.SetResource(State.Default); var issueTime = DateTime.Now; - Task.Delay(ReadyDelay).ContinueWith((previousTask) => + Task.Delay(this.ReadyDelay).ContinueWith((previousTask) => { if(client.LastActivityTime < issueTime) { client.Resources.SetResource(State.Initialized); - server.QueueMessage(client, Encoding.ASCII.GetBytes(welcomeMessage)); + server.QueueMessage(client, Encoding.ASCII.GetBytes(this.welcomeMessage)); client.SetAffinity(this); } }); @@ -70,7 +70,7 @@ namespace MTSC.ServerSide.Handlers { var request = FtpRequest.FromBytes(message.MessageBytes); var handled = false; - foreach(IFtpModule module in ftpModules) + foreach(var module in this.ftpModules) { if(module.HandleRequest(request, client, this, server)) { @@ -78,8 +78,10 @@ namespace MTSC.ServerSide.Handlers break; } } + return handled; } + return false; } diff --git a/MTSC/ServerSide/Handlers/HttpHandler.cs b/MTSC/ServerSide/Handlers/HttpHandler.cs index 2f81905..47892b4 100644 --- a/MTSC/ServerSide/Handlers/HttpHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpHandler.cs @@ -72,7 +72,7 @@ namespace MTSC.ServerSide.Handlers /// Message containing the response. public void QueueResponse(ClientData client, HttpResponse response) { - messageOutQueue.Enqueue(new Tuple(client, response)); + this.messageOutQueue.Enqueue(new Tuple(client, response)); } #endregion #region Interface Implementation @@ -109,31 +109,34 @@ namespace MTSC.ServerSide.Handlers var trimmedMessageBytes = message.MessageBytes.TrimTrailingNullBytes(); if (client.Resources.TryGetResource(out var fragmentedMessage)) { - byte[] previousBytes = fragmentedMessage.Message; - if (previousBytes.Length + trimmedMessageBytes.Length > MaximumRequestSize) + var previousBytes = fragmentedMessage.Message; + if (previousBytes.Length + trimmedMessageBytes.Length > this.MaximumRequestSize) { // Discard the message if it is too big - server.LogDebug($"Discarded message. Message size [{previousBytes.Length + trimmedMessageBytes.Length}] > [{MaximumRequestSize}]"); + server.LogDebug($"Discarded message. Message size [{previousBytes.Length + trimmedMessageBytes.Length}] > [{this.MaximumRequestSize}]"); client.Resources.RemoveResource(); - QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request disallowed because it exceeds [{MaximumRequestSize}] bytes!" }); + this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request disallowed because it exceeds [{this.MaximumRequestSize}] bytes!" }); return true; } - byte[] repackagingBuffer = new byte[previousBytes.Length + trimmedMessageBytes.Length]; + + var repackagingBuffer = new byte[previousBytes.Length + trimmedMessageBytes.Length]; Array.Copy(previousBytes, 0, repackagingBuffer, 0, previousBytes.Length); Array.Copy(trimmedMessageBytes, 0, repackagingBuffer, previousBytes.Length, trimmedMessageBytes.Length); messageBytes = repackagingBuffer; } else { - if (trimmedMessageBytes.Length > MaximumRequestSize) + if (trimmedMessageBytes.Length > this.MaximumRequestSize) { // Discard the message if it is too big - server.LogDebug($"Discarded message. Message size [{message.MessageBytes.Length}] > [{MaximumRequestSize}]"); - QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request disallowed because it exceeds [{MaximumRequestSize}] bytes!" }); + server.LogDebug($"Discarded message. Message size [{message.MessageBytes.Length}] > [{this.MaximumRequestSize}]"); + this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request disallowed because it exceeds [{this.MaximumRequestSize}] bytes!" }); return true; } + messageBytes = trimmedMessageBytes; } + messageBytes = messageBytes.TrimTrailingNullBytes(); var partialRequest = PartialHttpRequest.FromBytes(messageBytes); if (partialRequest.Complete) @@ -144,15 +147,16 @@ namespace MTSC.ServerSide.Handlers else { client.SetAffinity(this); - HandleIncompleteRequest(client, server, messageBytes, partialRequest); + this.HandleIncompleteRequest(client, server, messageBytes, partialRequest); if (partialRequest != null && partialRequest.Headers.ContainsHeader(RequestHeaders.Expect) && partialRequest.Headers[RequestHeaders.Expect].Equals("100-continue", StringComparison.OrdinalIgnoreCase)) { server.LogDebug("Returning 100-Continue"); var contResponse = new HttpResponse { StatusCode = HttpMessage.StatusCodes.Continue }; contResponse.Headers[HttpMessage.GeneralHeaders.Connection] = "keep-alive"; - QueueResponse(client, contResponse); + this.QueueResponse(client, contResponse); } + return true; } } @@ -195,8 +199,13 @@ namespace MTSC.ServerSide.Handlers { response.Headers[HttpMessage.GeneralHeaders.Connection] = "keep-alive"; } - foreach (var httpLogger in this.httpLoggers) httpLogger.LogRequest(server, this, client, request); - foreach (IHttpModule module in httpModules) + + foreach (var httpLogger in this.httpLoggers) + { + httpLogger.LogRequest(server, this, client, request); + } + + foreach (var module in this.httpModules) { try { @@ -217,7 +226,12 @@ namespace MTSC.ServerSide.Handlers throw; } } - foreach (var httpLogger in this.httpLoggers) httpLogger.LogResponse(server, this, client, response); + + foreach (var httpLogger in this.httpLoggers) + { + httpLogger.LogResponse(server, this, client, response); + } + this.QueueResponse(client, response); return true; } @@ -246,24 +260,26 @@ namespace MTSC.ServerSide.Handlers ///
void IHandler.Tick(Server server) { - while (messageOutQueue.Count > 0) + while (this.messageOutQueue.Count > 0) { - if (messageOutQueue.TryDequeue(out Tuple tuple)) + if (this.messageOutQueue.TryDequeue(out var tuple)) { server.QueueMessage(tuple.Item1, tuple.Item2.GetPackedResponse(true)); } } - foreach (IHttpModule module in httpModules) + + foreach (var module in this.httpModules) { module.Tick(server, this); } + foreach (var client in server.Clients) { if (client.Resources.TryGetResource(out var fragmentedMessage)) { - if ((DateTime.Now - fragmentedMessage.LastReceived) > FragmentsExpirationTime) + if ((DateTime.Now - fragmentedMessage.LastReceived) > this.FragmentsExpirationTime) { - QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request timed out in [{FragmentsExpirationTime.TotalMilliseconds}] ms!" }); + this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request timed out in [{this.FragmentsExpirationTime.TotalMilliseconds}] ms!" }); client.Resources.RemoveResource(); } } @@ -284,9 +300,9 @@ namespace MTSC.ServerSide.Handlers public void AddToMessage(byte[] bytes) { - byte[] newMessage = new byte[Message.Length + bytes.Length]; - Array.Copy(Message, newMessage, Message.Length); - Array.Copy(bytes, 0, newMessage, Message.Length, bytes.Length); + var newMessage = new byte[this.Message.Length + bytes.Length]; + Array.Copy(this.Message, newMessage, this.Message.Length); + Array.Copy(bytes, 0, newMessage, this.Message.Length, bytes.Length); } } } diff --git a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs index 632f55d..2b22900 100644 --- a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs @@ -1,9 +1,15 @@ -using MTSC.Common.Http; +using MTSC.Common; +using MTSC.Common.Http; +using MTSC.Common.Http.Attributes; using MTSC.Common.Http.RoutingModules; using MTSC.Common.Http.Telemetry; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; using static MTSC.Common.Http.HttpMessage; namespace MTSC.ServerSide.Handlers @@ -11,9 +17,9 @@ namespace MTSC.ServerSide.Handlers public sealed class HttpRoutingHandler : IHandler, IRunOnStartup { private static readonly Func alwaysEnabled = (server, request, client) => RouteEnablerResponse.Accept; - private readonly ConcurrentQueue> messageOutQueue = new ConcurrentQueue>(); + private readonly ConcurrentQueue> messageOutQueue = new(); private readonly List httpLoggers = new(); - private readonly Dictionary)>> moduleDictionary = new(); public TimeSpan FragmentsExpirationTime { get; set; } = TimeSpan.FromSeconds(15); @@ -22,10 +28,9 @@ namespace MTSC.ServerSide.Handlers public HttpRoutingHandler() { - foreach (HttpMethods method in (HttpMethods[])Enum.GetValues(typeof(HttpMethods))) + foreach (var method in (HttpMethods[])Enum.GetValues(typeof(HttpMethods))) { - this.moduleDictionary[method] = new Dictionary)>(); + this.moduleDictionary[method] = new List<(ExtendedUrl, Type, Func)>(); } } @@ -77,7 +82,7 @@ namespace MTSC.ServerSide.Handlers HttpMethods method, string uri) { - moduleDictionary[method].Remove(uri); + this.moduleDictionary[method].Remove(this.moduleDictionary[method].Where(tuple => tuple.Item1.Url == uri).First()); return this; } public HttpRoutingHandler WithMaximumSize(double size) @@ -101,7 +106,7 @@ namespace MTSC.ServerSide.Handlers /// Message containing the response. public void QueueResponse(ClientData client, HttpResponse response) { - messageOutQueue.Enqueue(new Tuple(client, response)); + this.messageOutQueue.Enqueue(new Tuple(client, response)); } void IHandler.ClientRemoved(Server server, ClientData client) { } @@ -123,11 +128,12 @@ namespace MTSC.ServerSide.Handlers fragmentedMessage.PartialRequest.Body.Length + message.MessageLength > this.MaximumRequestSize) { - this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{MaximumRequestSize}] bytes!" }); + this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{this.MaximumRequestSize}] bytes!" }); client.ResetAffinityIfMe(this); client.Resources.RemoveResource(); client.Resources.RemoveResourceIfExists(); } + fragmentedMessage.AddToMessage(bytesToBeAdded); request = fragmentedMessage.PartialRequest; } @@ -135,7 +141,7 @@ namespace MTSC.ServerSide.Handlers { if (message.MessageLength > this.MaximumRequestSize) { - this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{MaximumRequestSize}] bytes!" }); + this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{this.MaximumRequestSize}] bytes!" }); return false; } @@ -169,7 +175,7 @@ namespace MTSC.ServerSide.Handlers client.Resources.RemoveResource(); client.Resources.RemoveResource(); client.ResetAffinityIfMe(this); - this.HandleCompleteRequest(client, server, request.ToRequest(), mapping.MappedModule, mapping.RouteEnabler); + this.HandleCompleteRequest(client, server, request.ToRequest(), mapping.MappedModule, mapping.UrlValues, mapping.RouteEnabler); return true; } else @@ -180,19 +186,18 @@ namespace MTSC.ServerSide.Handlers } else { - if (this.moduleDictionary[request.Method].ContainsKey(request.RequestURI)) + if (this.TryMatchUrl(request.Method, request.RequestURI, out var urlValues, out var routeType, out var routeEnabler)) { - (var routeType, var routeEnabler) = this.moduleDictionary[request.Method][request.RequestURI]; var module = this.GetRoute(routeType, client, server); if (request.Complete) { var httpRequest = request.ToRequest(); client.ResetAffinityIfMe(this); - return this.HandleCompleteRequest(client, server, httpRequest, module, routeEnabler); + return this.HandleCompleteRequest(client, server, httpRequest, module, urlValues, routeEnabler); } else { - client.Resources.SetResource(new RequestMapping { MappedModule = module, RouteEnabler = routeEnabler }); + client.Resources.SetResource(new RequestMapping { MappedModule = module, RouteEnabler = routeEnabler, UrlValues = urlValues }); return true; } } @@ -213,11 +218,12 @@ namespace MTSC.ServerSide.Handlers { while (this.messageOutQueue.Count > 0) { - if (this.messageOutQueue.TryDequeue(out Tuple tuple)) + if (this.messageOutQueue.TryDequeue(out var tuple)) { server.QueueMessage(tuple.Item1, tuple.Item2.GetPackedResponse(true)); } } + foreach (var client in server.Clients) { if (client.Resources.TryGetResource(out var fragmentedMessage)) @@ -235,7 +241,7 @@ namespace MTSC.ServerSide.Handlers { foreach (var routes in this.moduleDictionary.Values) { - foreach ((var routeType, _) in routes.Values) + foreach ((_, var routeType, _) in routes) { server.ServiceManager.RegisterTransient(routeType, routeType); } @@ -253,19 +259,28 @@ namespace MTSC.ServerSide.Handlers Server server, HttpRequest request, HttpRouteBase module, + List urlValues, Func routeEnabler) { - foreach (var httpLogger in this.httpLoggers) httpLogger.LogRequest(server, this, client, request); + foreach (var httpLogger in this.httpLoggers) + { + httpLogger.LogRequest(server, this, client, request); + } var routeEnablerResponse = routeEnabler.Invoke(server, request, client); if (routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseAccept) { + SetModuleProperties(module, request, urlValues); try { module.CallHandleRequest(request).ContinueWith((task) => { - foreach (var httpLogger in this.httpLoggers) httpLogger.LogResponse(server, this, client, task.Result); - this.QueueResponse(client, task.Result); + foreach (var httpLogger in this.httpLoggers) + { + httpLogger.LogResponse(server, this, client, task.Result); + } + + this.QueueResponse(client, task.Result); }); } catch (Exception e) @@ -273,9 +288,14 @@ namespace MTSC.ServerSide.Handlers server.LogDebug("Exception: " + e.Message); server.LogDebug("Stacktrace: " + e.StackTrace); var response = new HttpResponse() { StatusCode = StatusCodes.InternalServerError }; - foreach (var httpLogger in this.httpLoggers) httpLogger.LogResponse(server, this, client, response); + foreach (var httpLogger in this.httpLoggers) + { + httpLogger.LogResponse(server, this, client, response); + } + this.QueueResponse(client, response); } + return true; } else if (routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseIgnore) @@ -284,8 +304,11 @@ namespace MTSC.ServerSide.Handlers } else if (routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseError) { - foreach (var httpLogger in this.httpLoggers) + foreach (var httpLogger in this.httpLoggers) + { httpLogger.LogResponse(server, this, client, (routeEnablerResponse as RouteEnablerResponse.RouteEnablerResponseError).Response); + } + this.QueueResponse(client, (routeEnablerResponse as RouteEnablerResponse.RouteEnablerResponseError).Response); return true; } @@ -309,6 +332,7 @@ namespace MTSC.ServerSide.Handlers private class RequestMapping { + public List UrlValues { get; set; } public HttpRouteBase MappedModule { get; set; } public Func RouteEnabler { get; set; } } @@ -324,6 +348,7 @@ namespace MTSC.ServerSide.Handlers (module as ISetHttpContext).SetClientData(client); (module as ISetHttpContext).SetServer(server); (module as ISetHttpContext).SetHttpRoutingHandler(this); + return module; } @@ -334,7 +359,124 @@ namespace MTSC.ServerSide.Handlers throw new InvalidOperationException($"{routeType.FullName} must be of type {typeof(HttpRouteBase).FullName}"); } - this.moduleDictionary[method][uri] = (routeType, routeEnabler); + this.moduleDictionary[method].Add((new ExtendedUrl(uri), routeType, routeEnabler)); + } + + private bool TryMatchUrl(HttpMethods method, string uri, out List urlValues, out Type type, out Func routeEnabler) + { + urlValues = null; + type = null; + routeEnabler = null; + + foreach((var url, var possibleType, var possibleRouteEnabler) in this.moduleDictionary[method]) + { + if (url.TryMatchUrl(uri, out var matchedValues)) + { + urlValues = matchedValues; + type = possibleType; + routeEnabler = possibleRouteEnabler; + return true; + } + } + + return false; + } + + private static void SetModuleProperties(HttpRouteBase module, HttpRequest httpRequest, List urlValues) + { + var properties = module.GetType().GetProperties(); + foreach (var property in properties) + { + foreach (var attribute in property.GetCustomAttributes(true)) + { + if (attribute is FromUrlAttribute fromUrlAttribute) + { + var maybeValue = urlValues.Where(val => val.Placeholder == fromUrlAttribute.Placeholder).FirstOrDefault(); + if (maybeValue is not null) + { + TryAssignValue(property, maybeValue.Value, module); + } + } + else if (attribute is FromBodyAttribute) + { + TryAssignValue(property, httpRequest.BodyString, module); + } + else if (attribute is FromHeadersAttribute fromHeadersAttribute) + { + var maybeValue = httpRequest.Headers.Where(kvp => kvp.Key == fromHeadersAttribute.HeaderName).FirstOrDefault(); + if (maybeValue.Value is not null) + { + TryAssignValue(property, maybeValue.Value, module); + } + } + } + } + } + + private static void TryAssignValue(PropertyInfo propertyInfo, string value, HttpRouteBase module) + { + object finalValue = null; + if (propertyInfo.PropertyType == typeof(string)) + { + finalValue = value; + } + else if (TryConvertWithTypeConverter(propertyInfo, value, out var typeConvertedValue)) + { + finalValue = typeConvertedValue; + } + else if (TryConvertWithJsonConvert(propertyInfo, value, out var jsonConvertedValue)) + { + finalValue = jsonConvertedValue; + } + + SetPropertyValue(propertyInfo, finalValue, module); + } + + private static void SetPropertyValue(PropertyInfo propertyInfo, object value, HttpRouteBase module) + { + if (propertyInfo.CanWrite is false) + { + var backingField = HelperFunctions.GetBackingField(propertyInfo); + backingField.SetValue(module, value); + } + else + { + propertyInfo.SetValue(module, value); + } + } + + private static bool TryConvertWithTypeConverter(PropertyInfo propertyInfo, string value, out object convertedValue) + { + try + { + var typeConverter = TypeDescriptor.GetConverter(propertyInfo.PropertyType); + if (typeConverter.CanConvertFrom(typeof(string))) + { + convertedValue = typeConverter.ConvertFrom(value); + return true; + } + } + catch + { + } + + convertedValue = null; + return false; + } + + private static bool TryConvertWithJsonConvert(PropertyInfo propertyInfo, string value, out object convertedValue) + { + try + { + convertedValue = JsonConvert.DeserializeObject(value, propertyInfo.PropertyType); + return true; + } + catch + { + } + + convertedValue = null; + return false; } } } diff --git a/MTSC/ServerSide/Handlers/WebsocketHandler.cs b/MTSC/ServerSide/Handlers/WebsocketHandler.cs index 86f0b48..1fcbd5b 100644 --- a/MTSC/ServerSide/Handlers/WebsocketHandler.cs +++ b/MTSC/ServerSide/Handlers/WebsocketHandler.cs @@ -17,7 +17,7 @@ namespace MTSC.ServerSide.Handlers private static readonly string WebsocketProtocolVersionKey = "Sec-WebSocket-Version"; private static readonly string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private static SHA1 sha1Provider = SHA1.Create(); - private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + private static RNGCryptoServiceProvider rng = new(); public enum SocketState { Initial, @@ -26,8 +26,8 @@ namespace MTSC.ServerSide.Handlers Closed } #region Fields - ConcurrentQueue> messageQueue = new ConcurrentQueue>(); - List websocketModules = new List(); + ConcurrentQueue> messageQueue = new(); + List websocketModules = new(); #endregion #region Public Methods /// @@ -46,13 +46,13 @@ namespace MTSC.ServerSide.Handlers /// Message to be sent to client. public void QueueMessage(ClientData client, byte[] message, WebsocketMessage.Opcodes opcode = WebsocketMessage.Opcodes.Text) { - WebsocketMessage sendMessage = new WebsocketMessage(); + var sendMessage = new WebsocketMessage(); sendMessage.Data = message; sendMessage.FIN = true; sendMessage.Masked = false; rng.GetBytes(sendMessage.Mask); sendMessage.Opcode = opcode; - messageQueue.Enqueue(new Tuple(client, sendMessage)); + this.messageQueue.Enqueue(new Tuple(client, sendMessage)); } /// /// Send a message to the client. @@ -61,7 +61,7 @@ namespace MTSC.ServerSide.Handlers /// Message packet. public void QueueMessage(ClientData client, WebsocketMessage message) { - messageQueue.Enqueue(new Tuple(client, message)); + this.messageQueue.Enqueue(new Tuple(client, message)); } /// /// Signals that the connetion is closing. @@ -69,11 +69,11 @@ namespace MTSC.ServerSide.Handlers /// Client to be disconnected. public void CloseConnection(ClientData client) { - WebsocketMessage websocketMessage = new WebsocketMessage(); + var websocketMessage = new WebsocketMessage(); websocketMessage.FIN = true; websocketMessage.Opcode = WebsocketMessage.Opcodes.Close; websocketMessage.Masked = false; - QueueMessage(client, websocketMessage); + this.QueueMessage(client, websocketMessage); } #endregion #region Handler Implementation @@ -93,7 +93,7 @@ namespace MTSC.ServerSide.Handlers var socketState = client.Resources.GetResource(); if (socketState == SocketState.Initial) { - PartialHttpRequest request = new PartialHttpRequest(message.MessageBytes); + var request = new PartialHttpRequest(message.MessageBytes); if(request.Method == HttpMessage.HttpMethods.Get && request.Headers.ContainsHeader(HttpMessage.GeneralHeaders.Connection) && request.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" && request.Headers.ContainsHeader(WebsocketProtocolVersionKey) && request.Headers[WebsocketProtocolVersionKey] == "13") @@ -102,15 +102,15 @@ namespace MTSC.ServerSide.Handlers /* * Prepare the handshake string. */ - string base64Key = request.Headers[WebsocketHeaderKey]; + var base64Key = request.Headers[WebsocketHeaderKey]; base64Key = base64Key.Trim(); - string handshakeKey = base64Key + GlobalUniqueIdentifier; - string returnBase64Key = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey))); + var handshakeKey = base64Key + GlobalUniqueIdentifier; + var returnBase64Key = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey))); /* * Prepare the response. */ - HttpResponse response = new HttpResponse(); + var response = new HttpResponse(); response.StatusCode = HttpMessage.StatusCodes.SwitchingProtocols; response.Headers[HttpMessage.GeneralHeaders.Upgrade] = "websocket"; response.Headers[HttpMessage.GeneralHeaders.Connection] = "Upgrade"; @@ -118,27 +118,29 @@ namespace MTSC.ServerSide.Handlers server.QueueMessage(client, response.GetPackedResponse(false)); client.Resources.SetResource(SocketState.Established); server.LogDebug("Websocket initialized " + client.TcpClient.Client.RemoteEndPoint.ToString()); - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { websocketModule.ConnectionInitialized(server, this, client); } + return true; } } else if(socketState == SocketState.Established) { - WebsocketMessage receivedMessage = new WebsocketMessage(message.MessageBytes); + var receivedMessage = new WebsocketMessage(message.MessageBytes); if (receivedMessage.Opcode == WebsocketMessage.Opcodes.Close) { - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { websocketModule.ConnectionClosed(server, this, client); } + client.ToBeRemoved = true; } else { - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { if (websocketModule.HandleReceivedMessage(server, this, client, receivedMessage)) { @@ -146,8 +148,10 @@ namespace MTSC.ServerSide.Handlers } } } + return true; } + return false; } @@ -163,18 +167,19 @@ namespace MTSC.ServerSide.Handlers void IHandler.Tick(Server server) { - while (messageQueue.Count > 0) + while (this.messageQueue.Count > 0) { - if (messageQueue.TryDequeue(out Tuple tuple)) + if (this.messageQueue.TryDequeue(out var tuple)) { server.QueueMessage(tuple.Item1, tuple.Item2.GetMessageBytes()); if (tuple.Item2.Opcode == WebsocketMessage.Opcodes.Close) { tuple.Item1.ResetAffinityIfMe(this); - foreach (IWebsocketModule websocketModule in websocketModules) + foreach (var websocketModule in this.websocketModules) { websocketModule.ConnectionClosed(server, this, tuple.Item1); } + tuple.Item1.ToBeRemoved = true; } } diff --git a/MTSC/ServerSide/Handlers/WebsocketRoutingHandler.cs b/MTSC/ServerSide/Handlers/WebsocketRoutingHandler.cs index d470f10..6e928b9 100644 --- a/MTSC/ServerSide/Handlers/WebsocketRoutingHandler.cs +++ b/MTSC/ServerSide/Handlers/WebsocketRoutingHandler.cs @@ -19,7 +19,7 @@ namespace MTSC.ServerSide.Handlers private const string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private static readonly Func alwaysEnabled = (server, message, client) => RouteEnablerResponse.Accept; private static readonly SHA1 sha1Provider = SHA1.Create(); - private static readonly RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + private static readonly RNGCryptoServiceProvider rng = new(); public enum SocketState { Initial, @@ -32,8 +32,8 @@ namespace MTSC.ServerSide.Handlers private DateTime ellapsedTime = DateTime.Now; private DateTime previousHeartbeatProc = DateTime.Now; private readonly Dictionary)> moduleDictionary = - new Dictionary)>(); - private readonly ConcurrentQueue> messageQueue = new ConcurrentQueue>(); + new(); + private readonly ConcurrentQueue> messageQueue = new(); #endregion #region Properties public bool HeartbeatEnabled { get; set; } @@ -85,7 +85,7 @@ namespace MTSC.ServerSide.Handlers public void QueueMessage(ClientData client, byte[] message, WebsocketMessage.Opcodes opcode = WebsocketMessage.Opcodes.Text) { - WebsocketMessage sendMessage = new WebsocketMessage + var sendMessage = new WebsocketMessage { Data = message, FIN = true, @@ -103,7 +103,7 @@ namespace MTSC.ServerSide.Handlers public void CloseConnection(ClientData client) { - WebsocketMessage websocketMessage = new WebsocketMessage + var websocketMessage = new WebsocketMessage { FIN = true, Opcode = WebsocketMessage.Opcodes.Close, @@ -142,6 +142,7 @@ namespace MTSC.ServerSide.Handlers server.LogDebug(e.Message + "\n" + e.StackTrace); return false; } + if (request.Method == HttpMessage.HttpMethods.Get && request.Headers.ContainsHeader(HttpMessage.GeneralHeaders.Connection) && request.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" && request.Headers.ContainsHeader(WebsocketProtocolVersionKey) && request.Headers[WebsocketProtocolVersionKey] == "13") @@ -151,7 +152,7 @@ namespace MTSC.ServerSide.Handlers this.QueueMessage(client, new HttpResponse { StatusCode = HttpMessage.StatusCodes.NotFound, BodyString = "URI not found" }.GetPackedResponse(true)); } - (var moduleType, var routeEnabler) = moduleDictionary[request.RequestURI]; + (var moduleType, var routeEnabler) = this.moduleDictionary[request.RequestURI]; var routeEnablerResponse = routeEnabler.Invoke(server, request.ToRequest(), client); if(routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseIgnore) { @@ -276,14 +277,14 @@ namespace MTSC.ServerSide.Handlers (this.ellapsedTime - this.previousHeartbeatProc) > this.HeartbeatFrequency) { this.previousHeartbeatProc = DateTime.Now; - this.QueueMessage(client, emptyData, WebsocketMessage.Opcodes.Ping); + this.QueueMessage(client, this.emptyData, WebsocketMessage.Opcodes.Ping); } } } while (this.messageQueue.Count > 0) { - if (this.messageQueue.TryDequeue(out Tuple tuple)) + if (this.messageQueue.TryDequeue(out var tuple)) { server.QueueMessage(tuple.Item1, tuple.Item2.GetMessageBytes()); if (tuple.Item2.Opcode == WebsocketMessage.Opcodes.Close) @@ -292,6 +293,7 @@ namespace MTSC.ServerSide.Handlers { route.CallConnectionClosed(); } + tuple.Item1.ToBeRemoved = true; } } diff --git a/MTSC/ServerSide/ResourceDictionary.cs b/MTSC/ServerSide/ResourceDictionary.cs index e800736..3a5ff92 100644 --- a/MTSC/ServerSide/ResourceDictionary.cs +++ b/MTSC/ServerSide/ResourceDictionary.cs @@ -5,39 +5,39 @@ namespace MTSC.ServerSide { public class ResourceDictionary : IDisposable { - private Dictionary Resources = new Dictionary(); + private Dictionary Resources = new(); public void RemoveResourceIfExists() { - if (Resources.ContainsKey(typeof(TValue))) + if (this.Resources.ContainsKey(typeof(TValue))) { - Resources.Remove((typeof(TValue))); + this.Resources.Remove((typeof(TValue))); } } public void RemoveResource() { - Resources.Remove(typeof(TValue)); + this.Resources.Remove(typeof(TValue)); } public void SetResource(TValue value) { - Resources[typeof(TValue)] = value; + this.Resources[typeof(TValue)] = value; } public TValue GetResource() { - return (TValue)Resources[typeof(TValue)]; + return (TValue)this.Resources[typeof(TValue)]; } public bool Contains() { - return Resources.ContainsKey(typeof(TValue)); + return this.Resources.ContainsKey(typeof(TValue)); } public bool TryGetResource(out TValue value) { - if (Resources.TryGetValue(typeof(TValue), out object objValue)) + if (this.Resources.TryGetValue(typeof(TValue), out var objValue)) { value = (TValue)objValue; return true; @@ -54,20 +54,21 @@ namespace MTSC.ServerSide protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!this.disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). } - foreach(var value in Resources.Values) + foreach(var value in this.Resources.Values) { (value as IDisposable)?.Dispose(); } - Resources.Clear(); - Resources = null; - disposedValue = true; + + this.Resources.Clear(); + this.Resources = null; + this.disposedValue = true; } } @@ -82,7 +83,7 @@ namespace MTSC.ServerSide public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); + this.Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } diff --git a/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs b/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs index 82c3edf..5d2e2da 100644 --- a/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs +++ b/MTSC/ServerSide/Schedulers/TaskAwaiterScheduler.cs @@ -9,12 +9,13 @@ namespace MTSC.ServerSide.Schedulers { public void ScheduleHandling(List<(ClientData, IConsumerQueue)> clientsQueues, Action> messageHandlingProcedure) { - List tasks = new List(); + var tasks = new List(); foreach(var tuple in clientsQueues) { (var client, var messageQueue) = tuple; tasks.Add(Task.Run(() => messageHandlingProcedure.Invoke(client, messageQueue))); } + Task.WaitAll(tasks.ToArray()); } } diff --git a/MTSC/ServerSide/Server.cs b/MTSC/ServerSide/Server.cs index 05a867d..5d76c62 100644 --- a/MTSC/ServerSide/Server.cs +++ b/MTSC/ServerSide/Server.cs @@ -39,9 +39,9 @@ namespace MTSC.ServerSide private readonly IServiceManager serviceManager = new ServiceManager(); #endregion #region Private Properties - private IConsumerQueue ConsumerClientQueue { get => addQueue; } - private IProducerQueue ProducerClientQueue { get => addQueue; } - private IConsumerQueue<(ClientData, byte[])> ConsumerMessageOutQueue { get => messageOutQueue; } + private IConsumerQueue ConsumerClientQueue { get => this.addQueue; } + private IProducerQueue ProducerClientQueue { get => this.addQueue; } + private IConsumerQueue<(ClientData, byte[])> ConsumerMessageOutQueue { get => this.messageOutQueue; } #endregion #region Public Properties /// @@ -55,7 +55,7 @@ namespace MTSC.ServerSide /// /// Queue of destinations and messages to be processed /// - public IProducerQueue<(ClientData, byte[])> MessageOutQueue { get => messageOutQueue; } + public IProducerQueue<(ClientData, byte[])> MessageOutQueue { get => this.messageOutQueue; } /// /// Duration until ssl authentication gives up during the authentication process /// @@ -87,7 +87,7 @@ namespace MTSC.ServerSide /// /// Returns the state of the server. /// - public bool Running { get => listener != null; } + public bool Running { get => this.listener != null; } /// /// If set to true, requests client certificates. /// @@ -275,7 +275,7 @@ namespace MTSC.ServerSide /// This server object public Server WithClientCertificate(bool requestCertificate) { - RequestClientCertificate = requestCertificate; + this.RequestClientCertificate = requestCertificate; return this; } /// @@ -422,6 +422,7 @@ namespace MTSC.ServerSide return handler as T; } } + return null; } /// @@ -438,6 +439,7 @@ namespace MTSC.ServerSide return exceptionHandler as T; } } + return null; } /// @@ -454,6 +456,7 @@ namespace MTSC.ServerSide return logger as T; } } + return null; } /// @@ -470,6 +473,7 @@ namespace MTSC.ServerSide return serverMonitor as T; } } + return null; } /// @@ -479,7 +483,7 @@ namespace MTSC.ServerSide /// Message to be sent. public void QueueMessage(ClientData target, byte[] message) { - (MessageOutQueue as IProducerQueue<(ClientData, byte[])>).Enqueue((target, message)); + (this.MessageOutQueue as IProducerQueue<(ClientData, byte[])>).Enqueue((target, message)); } /// /// Adds a message to be logged by the associated loggers. @@ -487,7 +491,7 @@ namespace MTSC.ServerSide /// Message to be logged public void Log(string log) { - foreach (ILogger logger in this.loggers) + foreach (var logger in this.loggers) { if (logger.Log(log)) { @@ -501,7 +505,7 @@ namespace MTSC.ServerSide /// Debug message to be logged public void LogDebug(string debugMessage) { - foreach (ILogger logger in this.loggers) + foreach (var logger in this.loggers) { if (logger.LogDebug(debugMessage)) { @@ -527,7 +531,7 @@ namespace MTSC.ServerSide this.serviceManager.RegisterServiceManager(); this.serviceManager.RegisterSingleton(sp => this); DateTime startLoopTime; - while (running) + while (this.running) { startLoopTime = DateTime.Now; /* @@ -570,7 +574,7 @@ namespace MTSC.ServerSide { this.Log("Accepted new connection: " + client.TcpClient.Client.RemoteEndPoint.ToString()); this.clients.Add(client); - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { if (handler.HandleClient(this, client)) { @@ -593,7 +597,7 @@ namespace MTSC.ServerSide /* * Iterate through all the handlers, running periodic operations. */ - foreach(IHandler handler in this.handlers) + foreach(var handler in this.handlers) { this.TickHandler(handler); } @@ -606,7 +610,7 @@ namespace MTSC.ServerSide /* * Call the usage monitors and let them scale or determine current resource usage. */ - foreach (IServerUsageMonitor usageMonitor in this.serverUsageMonitors) + foreach (var usageMonitor in this.serverUsageMonitors) { try { @@ -618,6 +622,7 @@ namespace MTSC.ServerSide } } } + this.listener.Stop(); foreach (var client in this.Clients) { @@ -634,6 +639,7 @@ namespace MTSC.ServerSide } } } + this.listener = null; } /// @@ -641,16 +647,16 @@ namespace MTSC.ServerSide /// public Task RunAsync() { - return Task.Run(Run); + return Task.Run(this.Run); } /// /// Stop the server. /// public void Stop() { - if (running) + if (this.running) { - running = false; + this.running = false; } } #endregion @@ -667,14 +673,16 @@ namespace MTSC.ServerSide */ continue; } + try { - Message sendMessage = CommunicationPrimitives.BuildMessage(bytes); - for (int i = this.handlers.Count - 1; i >= 0; i--) + var sendMessage = CommunicationPrimitives.BuildMessage(bytes); + for (var i = this.handlers.Count - 1; i >= 0; i--) { - IHandler handler = handlers[i]; + var handler = this.handlers[i]; handler.HandleSendMessage(this, client, ref sendMessage); } + CommunicationPrimitives.SendMessage(client.TcpClient, sendMessage, client.SslStream); (client as IActiveClient).UpdateLastActivity(); this.LogDebug("Sent message to " + client.TcpClient.Client.RemoteEndPoint.ToString() + @@ -688,21 +696,23 @@ namespace MTSC.ServerSide } private void CheckAndRemoveInactiveClients() { - foreach (ClientData client in this.Clients) + foreach (var client in this.Clients) { if (!client.TcpClient.Connected || client.ToBeRemoved) { this.toRemove.Add(client); } } - foreach (ClientData client in this.toRemove) + + foreach (var client in this.toRemove) { try { - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { handler.ClientRemoved(this, client); } + this.LogDebug("Client removed: " + client.TcpClient?.Client?.RemoteEndPoint?.ToString()); client.Dispose(); } @@ -710,8 +720,10 @@ namespace MTSC.ServerSide { this.HandleException(e); } + this.clients.Remove(client); } + this.toRemove.Clear(); } private void TickHandler(IHandler handler) @@ -785,6 +797,7 @@ namespace MTSC.ServerSide { this.HandleException(e); } + try { handler.HandleReceivedMessage(this, client, message); @@ -796,7 +809,7 @@ namespace MTSC.ServerSide } private void HandleClientMessage(ClientData client, Message message) { - foreach (IHandler handler in this.handlers) + foreach (var handler in this.handlers) { try { @@ -810,7 +823,8 @@ namespace MTSC.ServerSide this.HandleException(e); } } - foreach (IHandler handler in this.handlers) + + foreach (var handler in this.handlers) { try { @@ -838,7 +852,7 @@ namespace MTSC.ServerSide this.EncryptionPolicy); client.SslStream = sslStream; - if(sslStream.AuthenticateAsServerAsync(this.certificate, this.RequestClientCertificate, this.SslProtocols, false).Wait(SslAuthenticationTimeout)) + if(sslStream.AuthenticateAsServerAsync(this.certificate, this.RequestClientCertificate, this.SslProtocols, false).Wait(this.SslAuthenticationTimeout)) { /* * Client authenticated in the alloted time @@ -863,7 +877,7 @@ namespace MTSC.ServerSide } private void HandleException(Exception exception) { - foreach (IExceptionHandler exceptionHandler in this.exceptionHandlers) + foreach (var exceptionHandler in this.exceptionHandlers) { if (exceptionHandler.HandleException(exception)) { diff --git a/MTSC/ServerSide/UsageMonitors/CPUUsageLimiter.cs b/MTSC/ServerSide/UsageMonitors/CPUUsageLimiter.cs index 0d64c73..5d0c828 100644 --- a/MTSC/ServerSide/UsageMonitors/CPUUsageLimiter.cs +++ b/MTSC/ServerSide/UsageMonitors/CPUUsageLimiter.cs @@ -17,10 +17,10 @@ namespace MTSC.ServerSide.UsageMonitors /// /// Set the CPU usage limit. Bounded between 0 and 100%. /// - public int CPUUsageLimit { get => cpuUsageLimit; + public int CPUUsageLimit { get => this.cpuUsageLimit; set { - cpuUsageLimit = Math.Max(Math.Min(value, 100), 0); + this.cpuUsageLimit = Math.Max(Math.Min(value, 100), 0); } } /// @@ -30,26 +30,26 @@ namespace MTSC.ServerSide.UsageMonitors /// This public CPUUsageLimiter SetCPUUsageLimit(int cpuUsageLimit) { - CPUUsageLimit = cpuUsageLimit; + this.CPUUsageLimit = cpuUsageLimit; return this; } void IServerUsageMonitor.Tick(Server server) { - PollCPUUsage(); - while(cpuUsage > cpuUsageLimit) + this.PollCPUUsage(); + while(this.cpuUsage > this.cpuUsageLimit) { - server.LogDebug($"Throttling server due to CPUUsage = {cpuUsage}%!"); + server.LogDebug($"Throttling server due to CPUUsage = {this.cpuUsage}%!"); Thread.Sleep(10); - PollCPUUsage(); + this.PollCPUUsage(); } } private async void PollCPUUsage() { - if (!polling) + if (!this.polling) { - polling = true; + this.polling = true; var startTime = DateTime.UtcNow; var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; await Task.Delay(500); @@ -59,8 +59,8 @@ namespace MTSC.ServerSide.UsageMonitors var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; var totalMsPassed = (endTime - startTime).TotalMilliseconds; var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); - cpuUsage = cpuUsageTotal * 100; - polling = false; + this.cpuUsage = cpuUsageTotal * 100; + this.polling = false; } } } diff --git a/MTSC/ServerSide/UsageMonitors/TickrateEnforcer.cs b/MTSC/ServerSide/UsageMonitors/TickrateEnforcer.cs index d25567a..a952019 100644 --- a/MTSC/ServerSide/UsageMonitors/TickrateEnforcer.cs +++ b/MTSC/ServerSide/UsageMonitors/TickrateEnforcer.cs @@ -26,7 +26,7 @@ namespace MTSC.ServerSide.UsageMonitors /// This public TickrateEnforcer SetTicksPerSecond(int ticksPerSecond) { - TicksPerSecond = ticksPerSecond; + this.TicksPerSecond = ticksPerSecond; return this; } @@ -37,20 +37,24 @@ namespace MTSC.ServerSide.UsageMonitors /// public TickrateEnforcer SetSilent(bool silent) { - Silent = silent; + this.Silent = silent; return this; } void IServerUsageMonitor.Tick(Server server) { - if((DateTime.Now - lastTickTime).TotalMilliseconds < 1000f / TicksPerSecond) + if((DateTime.Now - this.lastTickTime).TotalMilliseconds < 1000f / this.TicksPerSecond) { - int sleepTime = (int)Math.Ceiling(Math.Max(1000f / TicksPerSecond - (DateTime.Now - lastTickTime).TotalMilliseconds, 0)) + 1; - if(!Silent) + var sleepTime = (int)Math.Ceiling(Math.Max(1000f / this.TicksPerSecond - (DateTime.Now - this.lastTickTime).TotalMilliseconds, 0)) + 1; + if(!this.Silent) + { server.LogDebug($"Sleeping thread for {sleepTime} ms due to throttling!"); + } + Thread.Sleep(sleepTime); } - lastTickTime = DateTime.Now; + + this.lastTickTime = DateTime.Now; } } }