From 62389684bdbd2ef82126c736f0fe5d4bfec38ea8 Mon Sep 17 00:00:00 2001 From: Muhammad Ummar Iqbal Date: Tue, 22 Nov 2016 18:37:43 +0500 Subject: [PATCH 01/33] POST method fix, Now HttpHandler is called for POST --- IotWeb Portable/Http/HttpRequestProcessor.cs | 43 ++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/IotWeb Portable/Http/HttpRequestProcessor.cs b/IotWeb Portable/Http/HttpRequestProcessor.cs index 668cdc7..545fb46 100644 --- a/IotWeb Portable/Http/HttpRequestProcessor.cs +++ b/IotWeb Portable/Http/HttpRequestProcessor.cs @@ -97,12 +97,25 @@ public void ProcessHttpRequest(Stream input, Stream output) throw new HttpRequestEntityTooLargeException(); // Read the data in MemoryStream content = new MemoryStream(); + //23.08.2016 - Changes for supporting POST Method + int contentCopyCounter = 0; + int bodyReadCount = 0; while (m_connected && (content.Length != length)) { - ReadData(input); + if (contentCopyCounter > 0) + { + int bytesToRead = ((length - bodyReadCount) > InputBufferSize) ? InputBufferSize : (length - bodyReadCount); + ReadData(input, bytesToRead); + } + + bodyReadCount += m_index; + content.Write(m_buffer, 0, m_index); ExtractBytes(m_index); + contentCopyCounter++; } + //23.08.2016 - End of Changes for supporting POST Method + // Did the connection drop while reading? if (!m_connected) return; @@ -391,6 +404,30 @@ private bool ReadLine(Stream input, out string line) ReadData(input); return false; } - #endregion - } + + /// + /// 23.08.2016 - Changes for supporting POST Method - Added a new overload for reading the content data from the input stream + /// Read data from the stream into the buffer. + /// + /// + /// + /// + private void ReadData(Stream input, int count) + { + try + { + int read = input.Read(m_buffer, 0, count); + m_index += read; + if (read == 0) + m_connected = false; + } + catch (Exception exp) + { + // Any error causes the connection to close + m_connected = false; + } + } + + #endregion + } } From c3971c425b89a9628ae12f47318f01e7ce4f6f5e Mon Sep 17 00:00:00 2001 From: Muhammad Ummar Iqbal Date: Tue, 22 Nov 2016 18:57:31 +0500 Subject: [PATCH 02/33] Fix for websocket handshake issue on Safari 10.0.1 (11602.2.14.0.7) with Invalid HTTP version string: HTTP/1.0 error --- IotWeb Portable/Http/BaseHttpServer.cs | 11 +++++++++++ IotWeb Portable/Http/HttpRequestProcessor.cs | 5 +++-- IotWeb Portable/Http/HttpResponse.cs | 15 ++++++++++++--- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/IotWeb Portable/Http/BaseHttpServer.cs b/IotWeb Portable/Http/BaseHttpServer.cs index fed52c8..56190a4 100644 --- a/IotWeb Portable/Http/BaseHttpServer.cs +++ b/IotWeb Portable/Http/BaseHttpServer.cs @@ -204,4 +204,15 @@ internal IWebSocketRequestHandler GetHandlerForWebSocket(string uri, out string } + + /////////////////////////////////Changes done locally to fix HTTP 1.1 on Safari 10 websocket error on 22.11.2016///////////////////// + /// + /// Defines HTTP version + /// + public enum HttpVersion + { + Ver1_0, + Ver1_1 + } + /////////////////////////////////Changes done locally to fix HTTP 1.1 on Safari 10 websocket error on 22.11.2016///////////////////// } diff --git a/IotWeb Portable/Http/HttpRequestProcessor.cs b/IotWeb Portable/Http/HttpRequestProcessor.cs index 545fb46..6c62eea 100644 --- a/IotWeb Portable/Http/HttpRequestProcessor.cs +++ b/IotWeb Portable/Http/HttpRequestProcessor.cs @@ -149,8 +149,9 @@ public void ProcessHttpRequest(Stream input, Stream output) { // Apply the after filters here m_server.ApplyAfterFilters(request, response, context); - // Write the response back to accept the connection - response.Send(output); + // Write the response back to accept the connection + /////////////////////////////////Changes done locally to fix HTTP 1.1 on Safari 10 websocket error on 22.11.2016///////////////////// + response.Send(output, HttpVersion.Ver1_1); output.Flush(); // Now we can process the websocket WebSocket ws = new WebSocket(input, output); diff --git a/IotWeb Portable/Http/HttpResponse.cs b/IotWeb Portable/Http/HttpResponse.cs index a1be42c..d35dbf0 100644 --- a/IotWeb Portable/Http/HttpResponse.cs +++ b/IotWeb Portable/Http/HttpResponse.cs @@ -21,15 +21,24 @@ internal HttpResponse() ResponseMessage = null; Content = new MemoryStream(); } - + /// /// Write the response to the output stream /// /// - internal void Send(Stream output) + internal void Send(Stream output, HttpVersion version = HttpVersion.Ver1_0) { // Write the response start line - WriteLine(output, String.Format("HTTP/1.0 {0} {1}", ResponseCode.ResponseCode(), ResponseCode.ResponseMessage(ResponseMessage))); + /////////////////////////////////Changes done locally to fix HTTP 1.1 on Safari 10 websocket error on 22.11.2016///////////////////// + if (version == HttpVersion.Ver1_0) + { + WriteLine(output, String.Format("HTTP/1.0 {0} {1}", ResponseCode.ResponseCode(), ResponseCode.ResponseMessage(ResponseMessage))); + } + else if(version == HttpVersion.Ver1_1) + { + WriteLine(output, String.Format("HTTP/1.1 {0} {1}", ResponseCode.ResponseCode(), ResponseCode.ResponseMessage(ResponseMessage))); + } + /////////////////////////////////Changes done locally to fix HTTP 1.1 on Safari 10 websocket error on 22.11.2016///////////////////// // Set content length accordingly Headers[HttpHeaders.ContentLength] = Content.Position.ToString(); // Write the headers From be1f83699b32efa37522d52277c27d230204c439 Mon Sep 17 00:00:00 2001 From: Muhammad Ummar Iqbal Date: Thu, 9 Mar 2017 17:16:34 +0500 Subject: [PATCH 03/33] README updated with bug fixes in this fork --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9a6bc57..135a2bb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Embedded HTTP and WebSocket Server for UWP/.NET 4.5 +This is a Fork of [IoTWeb] (https://github.com/sensaura-public/iotweb), and includes following bug fixes +* [HttpHandler was not being called for POST methods] (https://github.com/sensaura-public/iotweb/issues/6) +* Websocket handshake issue on Safari 10.0.1 (11602.2.14.0.7) with Invalid HTTP version string: HTTP/1.0 error. + The IotWeb class library allows you to embed a simple HTTP and WebSocket server into your C# application. The library supports both the .NET 4.5 framework as well as the Universal Windows Platform allowing you to target [Mono](http://www.mono-project.com/), Windows Desktop and [Windows 10 IoT Core](https://dev.windows.com/en-us/iot). The library is released under the [Creative Commons BY-SA license](http://creativecommons.org/licenses/by-sa/4.0/) - this means you can use the library in commercial and non-commercial projects as long as you provide attribution (a link to this page will be fine) and release any changes you make under the same license. From 61b09ed7f526d7dad0e9b0066dade616d7336541 Mon Sep 17 00:00:00 2001 From: mijaz Date: Wed, 15 Mar 2017 14:39:42 +0500 Subject: [PATCH 04/33] Session support implementation 1st version --- .../Helper/SessionFileStorageHandler.cs | 172 ++++++++++++++++++ IotWeb NET45/HttpServer.cs | 8 +- IotWeb NET45/IotWeb NET45.csproj | 5 + IotWeb Portable/Http/BaseHttpServer.cs | 15 +- IotWeb Portable/Http/HttpContext.cs | 7 +- IotWeb Portable/Http/HttpRequestProcessor.cs | 93 +++++++++- IotWeb Portable/Http/HttpResourceHandler.cs | 2 +- IotWeb Portable/Http/SessionDictionary.cs | 127 +++++++++++++ .../Interfaces/ISessionStorageHandler.cs | 21 +++ IotWeb Portable/IotWeb Portable.csproj | 14 ++ IotWeb Portable/Util/Enums.cs | 14 ++ IotWeb Portable/Util/SessionHandler.cs | 105 +++++++++++ .../Util/SessionStorageConfiguration.cs | 60 ++++++ IotWeb Portable/Util/Utilities.cs | 5 + IotWeb Portable/packages.config | 4 + .../Helper/SessionFileStorageHandler.cs | 171 +++++++++++++++++ IotWeb UWP/HttpServer.cs | 6 +- IotWeb UWP/IotWeb UWP.csproj | 1 + IotWeb UWP/project.json | 2 +- WebHost Desktop/Program.cs | 2 +- WebHost UWP/App.xaml.cs | 2 +- 21 files changed, 819 insertions(+), 17 deletions(-) create mode 100644 IotWeb NET45/Helper/SessionFileStorageHandler.cs create mode 100644 IotWeb Portable/Http/SessionDictionary.cs create mode 100644 IotWeb Portable/Interfaces/ISessionStorageHandler.cs create mode 100644 IotWeb Portable/Util/Enums.cs create mode 100644 IotWeb Portable/Util/SessionHandler.cs create mode 100644 IotWeb Portable/Util/SessionStorageConfiguration.cs create mode 100644 IotWeb Portable/packages.config create mode 100644 IotWeb UWP/Helper/SessionFileStorageHandler.cs diff --git a/IotWeb NET45/Helper/SessionFileStorageHandler.cs b/IotWeb NET45/Helper/SessionFileStorageHandler.cs new file mode 100644 index 0000000..9ed167b --- /dev/null +++ b/IotWeb NET45/Helper/SessionFileStorageHandler.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IotWeb.Common.Interfaces; +using IotWeb.Common.Util; +using System.Net.Mime; +using System.Security.AccessControl; +using Windows.Storage; + +using System.Windows.Forms; + +namespace IotWeb.Server.Helper +{ + public class SessionFileStorageHandler : ISessionStorageHandler + { + private readonly SessionStorageConfiguration _sessionStorageConfiguration; + private const string StorageFolder = "IoTSession"; + + public SessionFileStorageHandler(SessionStorageConfiguration sessionStorageConfiguration) + { + _sessionStorageConfiguration = sessionStorageConfiguration; + } + + public SessionStorageConfiguration SessionStorageConfiguration + { + get + { + return _sessionStorageConfiguration; + } + } + + public async Task DeleteSessionsAsync() + { + try + { + await Task.Run(() => + { + var path = GetStoragePath(); + + if (File.Exists(path)) + { + string[] files = Directory.GetFiles(GetStoragePath()); + + foreach (string file in files) + { + FileInfo fi = new FileInfo(file); + if (fi.LastAccessTime < DateTime.Now.AddMinutes(-SessionStorageConfiguration.SessionExpiredTime)) + fi.Delete(); + } + } + }); + + return true; + } + catch (Exception) + { + return false; + } + } + + public async Task DeleteSessionAsync(string sessionId) + { + try + { + await Task.Run(() => + { + string[] files = Directory.GetFiles(GetStoragePath()); + + foreach (string file in files) + { + if (file.Contains(sessionId)) + { + FileInfo fi = new FileInfo(file); + fi.Delete(); + break; + } + } + }); + + return true; + } + catch (Exception) + { + return false; + } + } + + public async Task GetDataAsync(string fileName) + { + try + { + string data = null; + + await Task.Run(() => { + if (!string.IsNullOrWhiteSpace(fileName)) + { + string fullFilePath = GetFilePath(fileName); + + if (File.Exists(fullFilePath)) + { + data = File.ReadAllText(fullFilePath); + } + } + }); + + return data; + } + catch (Exception e) + { + return null; + } + } + + public Task> GetSessionMetadata(string fileName) + { + throw new NotImplementedException(); + } + + public async Task SaveDataAsync(string fileName, string data) + { + try + { + bool isSaved = false; + await Task.Run(() => + { + var filePath = Path.GetDirectoryName(GetFilePath(fileName)); + if (!Directory.Exists(filePath)) + { + Directory.CreateDirectory(filePath); + } + + File.WriteAllText(GetFilePath(fileName), data); + isSaved = true; + }); + + return isSaved; + } + catch (Exception) + { + return false; + } + } + + private string GetStoragePath() + { + string fullStorageFilePath; + + fullStorageFilePath = !string.IsNullOrWhiteSpace(SessionStorageConfiguration.StoragePath) + ? SessionStorageConfiguration.StoragePath + : Path.Combine(Application.LocalUserAppDataPath, StorageFolder); + + return fullStorageFilePath; + } + + private string GetFilePath(string fileName) + { + string fullFilePath; + + fullFilePath = Path.Combine(GetStoragePath(), fileName); + + if (!string.IsNullOrWhiteSpace(SessionStorageConfiguration.SessionFileExtension)) + { + fullFilePath = fullFilePath + "." + SessionStorageConfiguration.SessionFileExtension; + } + + return fullFilePath; + } + } +} diff --git a/IotWeb NET45/HttpServer.cs b/IotWeb NET45/HttpServer.cs index 1dfdfcc..d31929f 100644 --- a/IotWeb NET45/HttpServer.cs +++ b/IotWeb NET45/HttpServer.cs @@ -1,12 +1,14 @@ using IotWeb.Common.Http; +using IotWeb.Common.Util; +using IotWeb.Server.Helper; namespace IotWeb.Server { public class HttpServer : BaseHttpServer { - public HttpServer(int port) - : base(new SocketServer(port)) - { + public HttpServer(int port, SessionStorageConfiguration sessionStorageConfiguration) + : base(new SocketServer(port), new SessionFileStorageHandler(sessionStorageConfiguration)) + { // No configuration required } } diff --git a/IotWeb NET45/IotWeb NET45.csproj b/IotWeb NET45/IotWeb NET45.csproj index 4fb68bd..be89826 100644 --- a/IotWeb NET45/IotWeb NET45.csproj +++ b/IotWeb NET45/IotWeb NET45.csproj @@ -32,13 +32,18 @@ + + + C:\Program Files (x86)\Windows Kits\10\References\Windows.Foundation.UniversalApiContract\2.0.0.0\Windows.Foundation.UniversalApiContract.winmd + + diff --git a/IotWeb Portable/Http/BaseHttpServer.cs b/IotWeb Portable/Http/BaseHttpServer.cs index 56190a4..8653c01 100644 --- a/IotWeb Portable/Http/BaseHttpServer.cs +++ b/IotWeb Portable/Http/BaseHttpServer.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Collections.Generic; +using IotWeb.Common.Interfaces; namespace IotWeb.Common.Http { @@ -17,7 +18,19 @@ public class BaseHttpServer : IServer public bool Running { get { return SocketServer.Running; } } - protected BaseHttpServer(ISocketServer server) + private ISessionStorageHandler _sessionStorageHandler; + public ISessionStorageHandler SessionStorageHandler + { + get { return _sessionStorageHandler; } + } + + protected BaseHttpServer(ISocketServer server, ISessionStorageHandler sessionStorageHandler) + : this(server) + { + _sessionStorageHandler = sessionStorageHandler; + } + + protected BaseHttpServer(ISocketServer server) { SocketServer = server; SocketServer.ConnectionRequested = ConnectionRequested; diff --git a/IotWeb Portable/Http/HttpContext.cs b/IotWeb Portable/Http/HttpContext.cs index ca2c65e..fd05cec 100644 --- a/IotWeb Portable/Http/HttpContext.cs +++ b/IotWeb Portable/Http/HttpContext.cs @@ -1,4 +1,7 @@ -namespace IotWeb.Common.Http +using System.Collections.Generic; +using IotWeb.Common.Util; + +namespace IotWeb.Common.Http { public class HttpContext : CaseInsensitiveDictionary { @@ -6,5 +9,7 @@ public class HttpContext : CaseInsensitiveDictionary /// Common name for session entry. /// public const string Session = "Session"; + + public SessionHandler SessionHandler; } } diff --git a/IotWeb Portable/Http/HttpRequestProcessor.cs b/IotWeb Portable/Http/HttpRequestProcessor.cs index 6c62eea..9c55034 100644 --- a/IotWeb Portable/Http/HttpRequestProcessor.cs +++ b/IotWeb Portable/Http/HttpRequestProcessor.cs @@ -1,9 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using IotWeb.Common.Util; +using Newtonsoft.Json; namespace IotWeb.Common.Http { @@ -35,9 +38,10 @@ private enum RequestParseState private const int InputBufferSize = 1024; private const byte CR = 0x0d; private const byte LF = 0x0a; + private const string SessionName = "IoTSession"; - // WebSocket header fields - private static string SecWebSocketKey = "Sec-WebSocket-Key"; + // WebSocket header fields + private static string SecWebSocketKey = "Sec-WebSocket-Key"; private static string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; private static string SecWebSocketVersion = "Sec-WebSocket-Version"; private static string SecWebSocketAccept = "Sec-WebSocket-Accept"; @@ -139,9 +143,75 @@ public void ProcessHttpRequest(Stream input, Stream output) } // We have at least a partial request, create the matching response context = new HttpContext(); - response = new HttpResponse(); - // Apply filters - if (m_server.ApplyBeforeFilters(request, response, context)) + + var sessionId = GetSessionIdentifier(request.Cookies); + + SessionHandler sessionHandler; + + if (string.IsNullOrEmpty(sessionId)) + { + var newSessionId = Utilities.GetNewSessionIdentifier(); + + sessionHandler = new SessionHandler(newSessionId, m_server.SessionStorageHandler); + context.SessionHandler = sessionHandler; + + var clearSessionTask = sessionHandler.ClearExpiredSessions(); + clearSessionTask.Wait(); + + var sessionSavedTask = sessionHandler.SaveSessionData(); + sessionSavedTask.Wait(); + var isSaved = sessionSavedTask.Result; + + if (isSaved) + { + + } + else + { + + } + } + else + { + sessionHandler = new SessionHandler(sessionId, m_server.SessionStorageHandler); + context.SessionHandler = sessionHandler; + + var clearSessionTask = sessionHandler.ClearExpiredSessions(); + clearSessionTask.Wait(); + + var sessionDataTask = sessionHandler.GetSessionData(sessionId); + sessionDataTask.Wait(); + var isRetrieved = sessionDataTask.Result; + + if (isRetrieved) + { + + } + else + { + + } + } + + response = new HttpResponse(); + + Cookie sessionCookie = new Cookie + { + Name = SessionName, + Value = context.SessionHandler.SessionId + }; + + //servername = Lcase(Request.ServerVariables("SERVER_NAME")) + //Response.Status = "301 Moved Permanently" + //Response.AddHeader "Location", "http://yoursite" + //Response.AddHeader "Referer", servername + //Response.End() + + response.Cookies.Add(sessionCookie); + + + // Apply filters + if (m_server.ApplyBeforeFilters(request, response, context)) { // Check for WebSocket upgrade IWebSocketRequestHandler wsHandler = UpgradeToWebsocket(request, response); @@ -166,7 +236,13 @@ public void ProcessHttpRequest(Stream input, Stream output) if (handler == null) throw new HttpNotFoundException(); handler.HandleRequest(partialUri, request, response, context); - } + + if (sessionHandler.IsChanged) + { + var sessionSavedTask = sessionHandler.SaveSessionData(); + sessionSavedTask.Wait(); + } + } } catch (HttpException ex) { @@ -429,6 +505,11 @@ private void ReadData(Stream input, int count) } } + private string GetSessionIdentifier(CookieCollection cookies) + { + return cookies[SessionName]?.Value; + } + #endregion } } diff --git a/IotWeb Portable/Http/HttpResourceHandler.cs b/IotWeb Portable/Http/HttpResourceHandler.cs index 2525133..0b55c48 100644 --- a/IotWeb Portable/Http/HttpResourceHandler.cs +++ b/IotWeb Portable/Http/HttpResourceHandler.cs @@ -29,7 +29,7 @@ private static string RequestToNamespace(string uri) return resourceNs; } - public void HandleRequest(string uri, HttpRequest request, HttpResponse response, HttpContext context) + public virtual void HandleRequest(string uri, HttpRequest request, HttpResponse response, HttpContext context) { if (request.Method != HttpMethod.Get) throw new HttpMethodNotAllowedException(); diff --git a/IotWeb Portable/Http/SessionDictionary.cs b/IotWeb Portable/Http/SessionDictionary.cs new file mode 100644 index 0000000..852a9b4 --- /dev/null +++ b/IotWeb Portable/Http/SessionDictionary.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IotWeb.Common.Http +{ + public class SessionDictionary : IDictionary + { + private IDictionary m_inner = new Dictionary(); + + public bool IsChanged { get; set; } + + public string this[string key] + { + get + { + return m_inner[key]; + } + + set + { + m_inner[key] = value; + IsChanged = true; + } + } + + public int Count + { + get + { + return m_inner.Count; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public ICollection Keys + { + get + { + return m_inner.Keys; + } + } + + public ICollection Values + { + get + { + return m_inner.Values; + } + } + + public SessionDictionary() + { + IsChanged = false; + } + + public void Add(KeyValuePair item) + { + m_inner.Add(item); + IsChanged = true; + } + + public void Add(string key, string value) + { + m_inner.Add(key, value); + IsChanged = true; + } + + public void Clear() + { + m_inner.Clear(); + IsChanged = true; + } + + public bool Contains(KeyValuePair item) + { + return m_inner.Contains(item); + } + + public bool ContainsKey(string key) + { + return m_inner.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + m_inner.CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() + { + return m_inner.GetEnumerator(); + } + + public bool Remove(KeyValuePair item) + { + IsChanged = true; + return m_inner.Remove(item); + } + + public bool Remove(string key) + { + IsChanged = true; + return m_inner.Remove(key); + } + + public bool TryGetValue(string key, out string value) + { + return m_inner.TryGetValue(key, out value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)m_inner).GetEnumerator(); + } + } +} diff --git a/IotWeb Portable/Interfaces/ISessionStorageHandler.cs b/IotWeb Portable/Interfaces/ISessionStorageHandler.cs new file mode 100644 index 0000000..2ff2b59 --- /dev/null +++ b/IotWeb Portable/Interfaces/ISessionStorageHandler.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using IotWeb.Common.Util; + +namespace IotWeb.Common.Interfaces +{ + public interface ISessionStorageHandler + { + SessionStorageConfiguration SessionStorageConfiguration + { + get; + } + + Task SaveDataAsync(string fileName, string data); + Task GetDataAsync(string fileName); + Task DeleteSessionsAsync(); + Task DeleteSessionAsync(string sessionId); + Task> GetSessionMetadata(string fileName); + + } +} \ No newline at end of file diff --git a/IotWeb Portable/IotWeb Portable.csproj b/IotWeb Portable/IotWeb Portable.csproj index 4ed53c2..0823662 100644 --- a/IotWeb Portable/IotWeb Portable.csproj +++ b/IotWeb Portable/IotWeb Portable.csproj @@ -50,16 +50,30 @@ + + + + + + + + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll + True + +