From fe6f69b8a2f6df4f5168f81ef7ec8aa68f97ad22 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Thu, 26 Nov 2015 23:22:10 -0600 Subject: [PATCH 01/14] Fixed HTTP headers loaded out of order When HTTP request was being built, the http headers were loaded out of order. The header that was used to build the RequestLine and URI required the 'Host' header which was loaded in a subsequent iteration of the While loop. This resulted in an exception being thrown when the URI was created. I resolved this by adding all of the other HTTP headers first and then loading the RequestLineAndURI after the While loop completed. Also, the method that was used to add the headers to the HeaderCollection in the RequestContext didn't have a constructor for the HeaderCollection class and so a null exception was thrown as soon as a header was attempted to add to the HeaderCollection. I created a default constructor for the RequestContext Class (Just as was done for the ResponseContext Class) which solved this issue. I noticed that the default code had a HeaderCollection constructor in the SetHeaders(), however this method wasn't utilized. --- DemoWeb/DemoWeb.csproj | 1 + Rinsen.WebServer/RequestContext.cs | 5 +++++ Rinsen.WebServer/RequestContextBuilder.cs | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/DemoWeb/DemoWeb.csproj b/DemoWeb/DemoWeb.csproj index 510a8b1..1108f0d 100644 --- a/DemoWeb/DemoWeb.csproj +++ b/DemoWeb/DemoWeb.csproj @@ -60,6 +60,7 @@ + diff --git a/Rinsen.WebServer/RequestContext.cs b/Rinsen.WebServer/RequestContext.cs index c039c06..f9b821f 100644 --- a/Rinsen.WebServer/RequestContext.cs +++ b/Rinsen.WebServer/RequestContext.cs @@ -7,6 +7,11 @@ namespace Rinsen.WebServer { public class RequestContext { + public RequestContext() + { + Headers = new HeaderCollection(); + } + public string RequestLine { get { return Method + " " + Uri.RawPath + " " + HttpVersion; } } public HeaderCollection Headers { get; private set; } diff --git a/Rinsen.WebServer/RequestContextBuilder.cs b/Rinsen.WebServer/RequestContextBuilder.cs index 5e0415d..10b43e5 100644 --- a/Rinsen.WebServer/RequestContextBuilder.cs +++ b/Rinsen.WebServer/RequestContextBuilder.cs @@ -28,6 +28,8 @@ private void GetHeaderPartsFromSocket(Socket socket, RequestContext requestConte var headerSize = 0; byte[] buffer = new byte[_serverContext.BufferSize]; var requestLineSet = false; + var RequestLineHeader = string.Empty; + while (socket.Available > 0) { @@ -55,13 +57,14 @@ private void GetHeaderPartsFromSocket(Socket socket, RequestContext requestConte } requestLineSet = true; - requestContext.SetRequestLineAndUri(headerString); + RequestLineHeader = headerString; } else { requestContext.SetHeader(headerString); } } + requestContext.SetRequestLineAndUri(RequestLineHeader); } } } From 558ab9505610cb62c8b15a9480a7c47d052706ef Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Mon, 30 Nov 2015 08:40:51 -0600 Subject: [PATCH 02/14] Added ToHex Extension Method Added a ToHexString extension method for Byte Arrays. Changed the NetworkInterfaceLocator() method so that it would use the new ToHexString extension method when writing out the MAC address so that it would be displayed in Hexidecimal instead of its previously displayed decimal value. --- DemoWeb/Program.cs | 15 +++++++++++++ .../Extensions/ExtensionMethods.cs | 21 +++++++++++++++++++ Rinsen.WebServer/NetworkInterfaceLocator.cs | 13 ++---------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/DemoWeb/Program.cs b/DemoWeb/Program.cs index f60a9d0..fbfc320 100644 --- a/DemoWeb/Program.cs +++ b/DemoWeb/Program.cs @@ -12,6 +12,21 @@ public static void Main() webServer.SetFileAndDirectoryService(new FileAndDirectoryService()); webServer.RouteTable.DefaultControllerName = "Default"; webServer.StartServer(); + + //var RootDirectory = "\\SD"; + //var RequiredDirectory = RootDirectory + "\\WWW"; + + //DirectoryInfo objDirectoryInfo = new DirectoryInfo(RootDirectory); + //Debug.Print("Current Directories..."); + //foreach (var objDir in objDirectoryInfo.GetDirectories()) + // Debug.Print(objDir.FullName); + + //Debug.Print("Creating Directory " + RequiredDirectory + "..."); + //Directory.CreateDirectory(RequiredDirectory); + + //Debug.Print("Now Current Directories..."); + //foreach (var objDir in objDirectoryInfo.GetDirectories()) + // Debug.Print(objDir.FullName); } } } \ No newline at end of file diff --git a/Rinsen.WebServer/Extensions/ExtensionMethods.cs b/Rinsen.WebServer/Extensions/ExtensionMethods.cs index 491d9df..b8f665d 100644 --- a/Rinsen.WebServer/Extensions/ExtensionMethods.cs +++ b/Rinsen.WebServer/Extensions/ExtensionMethods.cs @@ -66,5 +66,26 @@ public static int Count(this IEnumerable collection) } return count; } + + public static string ToHexString(this byte[] value, int index = 0) + { + return ToHexString(value, index, value.Length - index); + } + + public static string ToHexString(this byte[] value, int index, int length) + { + char[] c = new char[length * 3]; + byte b; + + for (int y = 0, x = 0; y < length; ++y, ++x) + { + b = (byte)(value[index + y] >> 4); + c[x] = (char)(b > 9 ? b + 0x37 : b + 0x30); + b = (byte)(value[index + y] & 0xF); + c[++x] = (char)(b > 9 ? b + 0x37 : b + 0x30); + c[++x] = '-'; + } + return new string(c, 0, c.Length - 1); + } } } diff --git a/Rinsen.WebServer/NetworkInterfaceLocator.cs b/Rinsen.WebServer/NetworkInterfaceLocator.cs index f3efe17..257a74f 100644 --- a/Rinsen.WebServer/NetworkInterfaceLocator.cs +++ b/Rinsen.WebServer/NetworkInterfaceLocator.cs @@ -2,7 +2,7 @@ using Microsoft.SPOT; using System.Net; using Microsoft.SPOT.Net.NetworkInformation; -using System.Text; +using Rinsen.WebServer.Extensions; namespace Rinsen.WebServer { @@ -22,16 +22,7 @@ public IPAddress Locate() { Debug.Print("Dns address: " + dnsAddress); } - var sb = new StringBuilder("Physical address: "); - var parts = 1; - foreach (var physicalAddress in networkInterface.PhysicalAddress) - { - sb.Append(physicalAddress); - if (parts < networkInterface.PhysicalAddress.Length) - sb.Append("-"); - parts++; - } - Debug.Print(sb.ToString()); + Debug.Print("Physical address: " + networkInterface.PhysicalAddress.ToHexString()); Debug.Print("Subnet mask: " + networkInterface.SubnetMask); count++; From db185009090b31c7606b651da8ae15af2ad1de29 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Tue, 1 Dec 2015 12:04:28 -0600 Subject: [PATCH 03/14] Implemented Json.NETMF for Json Serialization I was having a problem with serializing quotation marks using the JsonSerializer class. I implemented Json.NetMF package and implemented it's serialization method in the Controller Class instead; this corrected the problem. --- Rinsen.WebServer/Controller.cs | 3 ++- Rinsen.WebServer/Rinsen.WebServer.csproj | 7 +++++++ Rinsen.WebServer/packages.config | 4 ++++ packages/repositories.config | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Rinsen.WebServer/packages.config diff --git a/Rinsen.WebServer/Controller.cs b/Rinsen.WebServer/Controller.cs index b66cd17..0806aa7 100644 --- a/Rinsen.WebServer/Controller.cs +++ b/Rinsen.WebServer/Controller.cs @@ -31,7 +31,8 @@ public void SetHtmlResult(string data) public void SetJsonResult(object objectToSerialize) { HttpContext.Response.ContentType = "application/json"; - HttpContext.Response.Data = JsonSerializer.Serialize(objectToSerialize); + //HttpContext.Response.Data = JsonSerializer.Serialize(objectToSerialize); + HttpContext.Response.Data = Json.NETMF.JsonSerializer.SerializeObject(objectToSerialize); } /// diff --git a/Rinsen.WebServer/Rinsen.WebServer.csproj b/Rinsen.WebServer/Rinsen.WebServer.csproj index 8952b7e..6be6c57 100644 --- a/Rinsen.WebServer/Rinsen.WebServer.csproj +++ b/Rinsen.WebServer/Rinsen.WebServer.csproj @@ -78,6 +78,10 @@ + + ..\packages\Json.NetMF.1.3.0.0\lib\netmf43\Json.NetMF.dll + True + @@ -85,4 +89,7 @@ + + + \ No newline at end of file diff --git a/Rinsen.WebServer/packages.config b/Rinsen.WebServer/packages.config new file mode 100644 index 0000000..8acc6ab --- /dev/null +++ b/Rinsen.WebServer/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/repositories.config b/packages/repositories.config index 5124ce9..e329769 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -1,4 +1,5 @@  + \ No newline at end of file From 0a2e7bc98f8af07981f08352daeef2c454dc6688 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Tue, 8 Dec 2015 19:28:17 -0600 Subject: [PATCH 04/14] Integrated SDCard Class Integrated an SDCard Class to help stave off any possible concurrency update issues that may be encountered from reading and writing the SDCard. Implemented a FileController class that can be inherited instead of Controller when the Rinsen.WebServer.FileAndDirectoryServer Project is integrated with a Rinsen.WebServer project. --- DemoWeb/DemoWeb.csproj | 7 + DemoWeb/FilesController.cs | 41 +++ DemoWeb/Program.cs | 19 +- DemoWeb/SDCardManager.cs | 13 + NetduinoSDCard/ConsoleWrite.cs | 36 +++ NetduinoSDCard/NetduinoSDCard.csproj | 47 ++++ NetduinoSDCard/Program.cs | 22 ++ NetduinoSDCard/Properties/AssemblyInfo.cs | 25 ++ NetduinoSDCard/SDCard.cs | 263 ++++++++++++++++++ .../FileAndDirectoryService.cs | 46 +-- .../FileController.cs | 121 ++++++++ .../ISDCard.cs | 23 ++ ...en.WebServer.FileAndDirectoryServer.csproj | 3 + Rinsen.WebServer/RequestContext.cs | 23 +- Rinsen.WebServer/Result.cs | 3 +- RinsenWebServer.sln | 10 +- 16 files changed, 651 insertions(+), 51 deletions(-) create mode 100644 DemoWeb/FilesController.cs create mode 100644 DemoWeb/SDCardManager.cs create mode 100644 NetduinoSDCard/ConsoleWrite.cs create mode 100644 NetduinoSDCard/NetduinoSDCard.csproj create mode 100644 NetduinoSDCard/Program.cs create mode 100644 NetduinoSDCard/Properties/AssemblyInfo.cs create mode 100644 NetduinoSDCard/SDCard.cs create mode 100644 Rinsen.WebServer.FileAndDirectoryServer/FileController.cs create mode 100644 Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs diff --git a/DemoWeb/DemoWeb.csproj b/DemoWeb/DemoWeb.csproj index 1108f0d..51b28a0 100644 --- a/DemoWeb/DemoWeb.csproj +++ b/DemoWeb/DemoWeb.csproj @@ -40,6 +40,7 @@ + @@ -49,6 +50,7 @@ + @@ -60,9 +62,14 @@ + + + {66F7DA1E-2931-4E24-81F7-471B08488B96} + NetduinoSDCard + {6BFC7545-24EF-4C5B-9EDE-3EC99CDB86E4} Rinsen.WebServer diff --git a/DemoWeb/FilesController.cs b/DemoWeb/FilesController.cs new file mode 100644 index 0000000..adb0071 --- /dev/null +++ b/DemoWeb/FilesController.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.SPOT; + +using Rinsen.WebServer.FileAndDirectoryServer; +using Rinsen.WebServer; + +namespace DemoWeb +{ + public class FilesController : FileController + { + public void Index() + { + this.SDCardManager = new SDCardManager(Program.WORKINGDIRECTORY); + string strHTML = string.Empty; + + + try + { + strHTML = SDCardManager.ReadTextFile(SDCardManager.GetWorkingDirectoryPath() + "filemanager.html"); + strHTML = strHTML.Substring(1, strHTML.Length - 2); //If I don't remove the first character then the page doesn't get rendered as html... + } + catch (Exception objEx) + { + Debug.Print("Exception caught in FilesController:\r\n" + objEx.Message); + } + + SetHtmlResult(strHTML); + } + + public void Upload() + { + this.SDCardManager = new SDCardManager(Program.WORKINGDIRECTORY); + + if (HttpContext.Request.RequestType == EnumRequestType.Post) + { + var doFileUpload = RecieveFile(); + SetJsonResult(new Result { Success = true, Message = doFileUpload }); + } + } + } +} diff --git a/DemoWeb/Program.cs b/DemoWeb/Program.cs index fbfc320..9445cb3 100644 --- a/DemoWeb/Program.cs +++ b/DemoWeb/Program.cs @@ -4,29 +4,18 @@ namespace DemoWeb { public class Program { + public const string WORKINGDIRECTORY = @"\WWW"; + public static void Main() { // write your code here var webServer = new WebServer(); webServer.AddRequestFilter(new RequestFilter()); + var fileAndDirectoryService = new FileAndDirectoryService(); + fileAndDirectoryService.SetSDCard(new SDCardManager(WORKINGDIRECTORY)); webServer.SetFileAndDirectoryService(new FileAndDirectoryService()); webServer.RouteTable.DefaultControllerName = "Default"; webServer.StartServer(); - - //var RootDirectory = "\\SD"; - //var RequiredDirectory = RootDirectory + "\\WWW"; - - //DirectoryInfo objDirectoryInfo = new DirectoryInfo(RootDirectory); - //Debug.Print("Current Directories..."); - //foreach (var objDir in objDirectoryInfo.GetDirectories()) - // Debug.Print(objDir.FullName); - - //Debug.Print("Creating Directory " + RequiredDirectory + "..."); - //Directory.CreateDirectory(RequiredDirectory); - - //Debug.Print("Now Current Directories..."); - //foreach (var objDir in objDirectoryInfo.GetDirectories()) - // Debug.Print(objDir.FullName); } } } \ No newline at end of file diff --git a/DemoWeb/SDCardManager.cs b/DemoWeb/SDCardManager.cs new file mode 100644 index 0000000..bc3e883 --- /dev/null +++ b/DemoWeb/SDCardManager.cs @@ -0,0 +1,13 @@ +using System; +using Microsoft.SPOT; + +using Rinsen.WebServer.FileAndDirectoryServer; + +namespace DemoWeb +{ + public class SDCardManager : NetduinoSDCard.SDCard, ISDCard + { + public SDCardManager(string WorkingDirectory) + : base(WorkingDirectory) {} + } +} diff --git a/NetduinoSDCard/ConsoleWrite.cs b/NetduinoSDCard/ConsoleWrite.cs new file mode 100644 index 0000000..8e89f6b --- /dev/null +++ b/NetduinoSDCard/ConsoleWrite.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.SPOT; + +namespace NetduinoSDCard +{ + public class ConsoleWrite // todo this should be moved to a general library. + { + public static Object _PrintLock = new Object(); + public static Object _GCLock = new Object(); + + public static void Print(string message) + { + lock (_PrintLock) + { + Debug.Print(message); + } + } + + public static uint MemoryCollect(bool force) + { + uint freeMemory = 0; + lock (_GCLock) + { + freeMemory = Microsoft.SPOT.Debug.GC(force); + } + return freeMemory; + } + + public static void CollectMemoryAndPrint(bool force, int id) + { + uint freeMemory = MemoryCollect(force); + Print("Memory: " + freeMemory.ToString() + ", release workerid: " + id.ToString()); + + } + } +} diff --git a/NetduinoSDCard/NetduinoSDCard.csproj b/NetduinoSDCard/NetduinoSDCard.csproj new file mode 100644 index 0000000..f9c1024 --- /dev/null +++ b/NetduinoSDCard/NetduinoSDCard.csproj @@ -0,0 +1,47 @@ + + + + NetduinoSDCard + Exe + NetduinoSDCard + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {66F7DA1E-2931-4E24-81F7-471B08488B96} + v4.3 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + Netduino + USB + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NetduinoSDCard/Program.cs b/NetduinoSDCard/Program.cs new file mode 100644 index 0000000..51fc6ce --- /dev/null +++ b/NetduinoSDCard/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using SecretLabs.NETMF.Hardware; +using SecretLabs.NETMF.Hardware.Netduino; + +namespace NetduinoSDCard +{ + public class Program + { + public static void Main() + { + // write your code here + + + } + + } +} diff --git a/NetduinoSDCard/Properties/AssemblyInfo.cs b/NetduinoSDCard/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..00e30ca --- /dev/null +++ b/NetduinoSDCard/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NetduinoSDCard")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NetduinoSDCard")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NetduinoSDCard/SDCard.cs b/NetduinoSDCard/SDCard.cs new file mode 100644 index 0000000..6d7f28f --- /dev/null +++ b/NetduinoSDCard/SDCard.cs @@ -0,0 +1,263 @@ +using System; +using System.Net; +using System.IO; +using System.Net.Sockets; +using System.Text; + +namespace NetduinoSDCard +{ + public delegate void BytesDelegate(byte[] data); + + + public class SDCard // todo web log and error log + { + const int READ_CHUNK_SIZE = 1500;// + const int WRITE_CHUNK_SIZE = 1500;// + + public static Object SDCardLock = new Object(); + public const string MountDirectoryPath = @"\SD"; + + public SDCard() + { + } + public SDCard(string WorkingDirectory) + { + SetWorkingDirectoryInfo(WorkingDirectory); + } + + public DirectoryInfo WorkingDirectoryInfo { get; set; } + public void SetWorkingDirectoryInfo(string WorkingDirectory) + { + var strPath = MountDirectoryPath + WorkingDirectory; + CreateDirectory(strPath); + WorkingDirectoryInfo = new DirectoryInfo(strPath); + } + + public string GetWorkingDirectoryPath() + { + if (WorkingDirectoryInfo == null) + return MountDirectoryPath; + else + return WorkingDirectoryInfo.FullName + "\\"; + } + + public static string GetFullDirectoryPath(string folderName) + { + return MountDirectoryPath + folderName; + } + + public static string GetFileFullPath(string fileName)// todo allow for trailing slash on f + { + return MountDirectoryPath + "\\" + fileName; + } + + public static string GetFileFullPath(string directoryPath, string fileName) + { + if (directoryPath.LastIndexOf('\\') == directoryPath.Length - 1) + { + return MountDirectoryPath + fileName; + } + else + { + return directoryPath + "\\" + fileName; + } + } + + public static string GetFullPathFromUri(string uri) + { + return MountDirectoryPath + PathToBackSlash(uri); + } + + public void CreateFile(string path, string fileName)// needs trailing slash + { + string fileFullPath = path + fileName; + CreateDirectory(path); + lock (SDCardLock) + { + if (!File.Exists(path + fileName)) + { + FileStream fs = File.Create(fileFullPath); + fs.Close(); + } + } + } + + public void CreateDirectory(string fullPath) + { + lock (SDCardLock) + { + if (!Directory.Exists(fullPath)) + { + DirectoryInfo directoryInfo = Directory.CreateDirectory(fullPath); + } + } + } + + public bool WriteLine(string path, string fileName, FileMode fileMode, string text) + { + return Write(path, fileName, fileMode, text + "\n");// todo \r\n?? + } + + public bool Write(string path, string fileName, FileMode fileMode, string text) + { + byte[] buffer = Encoding.UTF8.GetBytes(text); + return Write(path, fileName, fileMode, buffer, buffer.Length); + } + + public bool Write(string path, string fileName, FileMode fileMode, byte[] bytes, int length) + { + string fileFullPath = path + fileName; + CreateFile(path, fileName); + lock (SDCardLock) + { + FileStream fs = new FileStream(fileFullPath, fileMode, FileAccess.Write, FileShare.None, length);// todo make buffersize a constant somewhere + fs.Write(bytes, 0, length); + fs.Close(); + } + return true; + } + + public bool WriteLines(string path, string fileName, string[] lines) + { + string fileFullPath = path + fileName; + CreateFile(path, fileName); + lock (SDCardLock) + { + FileStream fs = new FileStream(fileFullPath, FileMode.Append, FileAccess.Write, FileShare.None, WRITE_CHUNK_SIZE); + for (int i = 0; i < lines.Length - 1; i++) + { + var buf = Encoding.UTF8.GetBytes(lines[i] + "\r\n"); + fs.Write(buf, 0, buf.Length); + } + fs.Close(); + } + return true; + } + //http://forums.netduino.com/index.php?/topic/2394-memory-efficient-way-to-enumerate-an-array-of-fileinfo/page__p__16985__hl__%2Bsdcard+%2Benumerate__fromsearch__1#entry16985 + + public string ReadLine(string fullPath) + { + var FileContents = string.Empty; + + ConsoleWrite.Print("Reading file: " + fullPath); + lock (SDCardLock) + { + if (File.Exists(fullPath)) + using (StreamReader objStreamReader = new StreamReader(fullPath)) + FileContents = objStreamReader.ReadLine(); + else + throw new IOException("File Not Found!"); + } + return FileContents; + + } + + public string ReadTextFile(string fullPath) + { + var FileContents = string.Empty; + + ConsoleWrite.Print("Reading file: " + fullPath); + lock (SDCardLock) + { + if (File.Exists(fullPath)) + using (StreamReader objStreamReader = new StreamReader(fullPath)) + FileContents = objStreamReader.ReadToEnd(); + else + throw new IOException("File Not Found!"); + } + return FileContents; + } + + public void SendFile(string fullPath, Socket socket) + { + ConsoleWrite.Print("Reading file: " + fullPath); + bool chunkHasBeenRead = false; + int totalBytesRead = 0; + lock (SDCardLock) + { + if (File.Exists(fullPath)) + { + using (FileStream inputStream = new FileStream(fullPath, FileMode.Open)) + { + byte[] readBuffer = new byte[READ_CHUNK_SIZE]; + while (true) + { + // Send the file a few bytes at a time + int bytesRead = inputStream.Read(readBuffer, 0, readBuffer.Length); + if (bytesRead == 0) + break; + socket.Send(readBuffer, 0, bytesRead, SocketFlags.None); + //sendDataCallback(readBuffer); + totalBytesRead += bytesRead; + } + } + chunkHasBeenRead = true; + } + else + { + chunkHasBeenRead = false; + } + } + if (chunkHasBeenRead == true) + ConsoleWrite.Print("Sending " + totalBytesRead.ToString() + " bytes..."); + else + ConsoleWrite.Print("Failed to read chunk, full path: " + fullPath); + } + + const int _PostRxBufferSize = 1500; + public byte[] GetMoreBytes(Socket connectionSocket, out int count) + { + byte[] result = new byte[_PostRxBufferSize]; + SocketFlags socketFlags = new SocketFlags(); + count = connectionSocket.Receive(result, result.Length, socketFlags); + return result; + } + + public static string Replace(string input, char[] oldText, string newText) + { + string result = ""; + string[] split = input.Split(oldText); + for (int i = 0; i < split.Length - 1; i++) + { + result += split[i] + newText; + } + result += split[split.Length - 1]; + return result; + } + + public static string PathToBackSlash(string input) + { + string result = Replace(input, new char[] { '/' }, "\\"); + return result; + } + + public long GetFileSize(string directoryPath, string fileName) + { + long result = 0; + // ConsoleWrite.Print("Get File Size: " + fileNameAndPath); + lock (SDCardLock) + { + string fileNameAndPath = GetFileFullPath(directoryPath, fileName); + if (Directory.Exists(directoryPath)) + { + if (File.Exists(fileNameAndPath)) + { + using (FileStream inputStream = new FileStream(fileNameAndPath, FileMode.Open)) + { + result = inputStream.Length; + } + } + } + } + return result; + } + public long GetFileSize(string fileNameAndPath) + { + int lastIndexOf = fileNameAndPath.LastIndexOf('\\'); + string directoryName = fileNameAndPath.Substring(0, lastIndexOf); + string fileName = fileNameAndPath.Substring(lastIndexOf + 1); + return GetFileSize(directoryName, fileName); + } + + } +} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs index 0e1b468..96ce0c4 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs @@ -1,10 +1,16 @@ using Rinsen.WebServer.Http; using System.IO; using System.Net.Sockets; + +using System.Collections; + + namespace Rinsen.WebServer.FileAndDirectoryServer { public class FileAndDirectoryService : IFileAndDirectoryService { + private ISDCard SDCardManager { get; set; } + public void SetFileNameAndPathIfFileExists(ServerContext serverContext, HttpContext httpContext) { var fileFullName = serverContext.FileServerBasePath + httpContext.Request.Uri.LocalPath; @@ -60,24 +66,7 @@ public void SetFileNameAndPathIfFileExists(ServerContext serverContext, HttpCont public void SendFile(ServerContext serverContext, HttpContext httpContext) { - using (var fileStream = new FileStream(httpContext.Response.FileFullName, FileMode.Open, FileAccess.Read, FileShare.None)) - { - var fileLength = fileStream.Length; - byte[] buf = new byte[2048]; - for (long bytesSent = 0; bytesSent < fileLength; ) - { - // Determines amount of data left. - long bytesToRead = fileLength - bytesSent; - bytesToRead = bytesToRead < 2048 ? bytesToRead : 2048; - // Reads the data. - fileStream.Read(buf, 0, (int)bytesToRead); - // Writes data to browser - httpContext.Socket.Send(buf, 0, (int)bytesToRead, SocketFlags.None); - - // Updates bytes read. - bytesSent += bytesToRead; - } - } + SDCardManager.SendFile(httpContext.Response.FileFullName, httpContext.Socket); } public bool TryGetDirectoryResultIfDirectoryExists(ServerContext serverContext, HttpContext httpContext) @@ -97,23 +86,14 @@ public bool TryGetDirectoryResultIfDirectoryExists(ServerContext serverContext, return false; } - public string GetFileServiceBasePath() { - string basePath = "\\SD\\WWW"; - var directory = new DirectoryInfo(basePath); - if (directory.Exists) - { - return basePath; - } - - basePath = "\\WINFS\\WWW"; - directory = new DirectoryInfo(basePath); - if (directory.Exists) - { - return basePath; - } - return string.Empty; + return SDCardManager.GetWorkingDirectoryPath(); + } + + public void SetSDCard(ISDCard sdCard) + { + SDCardManager = sdCard; } } } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs new file mode 100644 index 0000000..553ea9a --- /dev/null +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -0,0 +1,121 @@ +using System; +using Microsoft.SPOT; + +using System.Collections; +using System.Text.RegularExpressions; +using System.Text; +namespace Rinsen.WebServer.FileAndDirectoryServer +{ + public class FileController : Controller + { + public ISDCard SDCardManager { get; set; } + + public string RecieveFile() + { + var request = HttpContext.Request; + Hashtable formVariables = new Hashtable(); + if (request.RequestType == EnumRequestType.Post) + { + + // This form should be short so get it all data now, in one go. + // assume no content sent with first packet todo fix + int contentLengthFromHeader = int.Parse(request.Headers["Content-Length"].ToString()); + int contentLengthReceived = 0; + string requestContent = ""; + string fileName = ""; + string boundaryPattern = ""; + string fileDirectoryPath = SDCardManager.GetWorkingDirectoryPath(); + { + if (contentLengthReceived < contentLengthFromHeader)// get next packet, this should have the start of any file in it. // todo put timeout + { + int count = 0; + byte[] data = SDCardManager.GetMoreBytes(HttpContext.Socket, out count); + requestContent += new string(Encoding.UTF8.GetChars(data, contentLengthReceived, count)); + contentLengthReceived += count; + } + + string strTemp = request.Headers["Content-Type"].Split(new char[] { ';' })[1].Split(new char[] { '=' })[1].ToString(); + var ContentType = request.Headers["Content-Type"]; //will have a value like "multipart/form-data; boundary=---------------------------2261521032598" + var boundarystring = ContentType.Split(new char[] { ';' })[1]; //gives me " boundary=---------------------------2261521032598" + string boundary = boundarystring.Split(new char[] { '=' })[1].ToString(); //gives me "---------------------------2261521032598" + // int nextBoundaryIndex = requestContent.IndexOf(boundary);// todo boundaries can change + boundaryPattern = "--" + boundary;//"#\n\n(.*)\n--$boundary#" + Regex MyRegex = new Regex(boundaryPattern, RegexOptions.Multiline); + string[] split = MyRegex.Split(requestContent); + for (int i = 0; i < split.Length; i++) + { + const string _ContentDispositionSearch = "Content-Disposition: form-data; name=\""; + int pos = split[i].IndexOf(_ContentDispositionSearch); + if (pos >= 0) + { + string remainder = split[i].Substring(pos + _ContentDispositionSearch.Length); + // ConsoleWrite.Print(remainder); + string[] nameSplit = remainder.Split(new char[] { '\"' }, 2); + string name = nameSplit[0]; + if (nameSplit[1][0] == ';') + {// file + int fileDataSeparatorIndex = nameSplit[1].IndexOf("\r\n\r\n"); // "\r\n\r\n" data starts after double new line + if (fileDataSeparatorIndex >= 0) + { + string fileNameSection = nameSplit[1].Substring(0, fileDataSeparatorIndex); + string[] fileNameSplit = fileNameSection.Split(new char[] { '\"' }); + formVariables.Add("fileName", fileNameSplit[1]); + fileName = fileNameSplit[1]; + string fileDataPart1 = nameSplit[1].Substring(fileDataSeparatorIndex + 4); + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Create, fileDataPart1); + } + } + else + {// normal form variable + StringBuilder value = new StringBuilder(nameSplit[1]); + value = value.Replace("\r", "").Replace("\n", "").Replace("/", "\\"); + if (nameSplit[0] == "path") + { + fileDirectoryPath = SDCardManager.GetWorkingDirectoryPath() + value + "\\"; + } + formVariables.Add(nameSplit[0], value); + + + } + } + + } + } + + // get the rest of the file and send to sd card + if (fileName.Length > 0)// todo what other checks + { + while (contentLengthReceived < contentLengthFromHeader)// get next packet, this should have the start of any file in it. // todo put timeout + { + byte[] data = null; + int count = 0; + { + data = SDCardManager.GetMoreBytes(HttpContext.Socket, out count); + contentLengthReceived += count; + //requestContent = new string(Encoding.UTF8.GetChars(data, 0, count)); + } + //ConsoleWrite.CollectMemoryAndPrint(true, System.Threading.Thread.CurrentThread.ManagedThreadId); + int boundaryPosition = requestContent.IndexOf(boundaryPattern); + if (boundaryPosition < 0) + {// no boundary so write all the bytes + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, data, count); + } + else + {// boundary so write some of the bytes via a string + string fileContent = requestContent.Substring(0, boundaryPosition); + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, fileContent); + } + // todo other params following + + } + } + + } + string message = string.Empty; + foreach (string key in formVariables.Keys) + message += "

" + key + ": " + formVariables[key].ToString() + "

"; + + return message; + } + } +} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs b/Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs new file mode 100644 index 0000000..9297d9b --- /dev/null +++ b/Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.SPOT; + +using System.Net.Sockets; +using System.IO; + +namespace Rinsen.WebServer.FileAndDirectoryServer +{ + public interface ISDCard + { + string GetWorkingDirectoryPath(); + + void SendFile(string fullPath, Socket socket); + + byte[] GetMoreBytes(Socket connectionSocket, out int count); + + bool Write(string path, string fileName, FileMode fileMode, string text); + + bool Write(string path, string fileName, FileMode fileMode, byte[] bytes, int length); + + string ReadTextFile(string fullPath); + } +} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index f1b0a8a..ae5527c 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -35,6 +35,8 @@ + + @@ -43,6 +45,7 @@ +
diff --git a/Rinsen.WebServer/RequestContext.cs b/Rinsen.WebServer/RequestContext.cs index f9b821f..68584ea 100644 --- a/Rinsen.WebServer/RequestContext.cs +++ b/Rinsen.WebServer/RequestContext.cs @@ -5,6 +5,9 @@ namespace Rinsen.WebServer { + public enum EnumRequestType { Get, Post, Put, Undefined } //add as neccessary + //public enum EnumContentType { Text, Binary, MultipartFormData, Undefined } //add as neccessary + public class RequestContext { public RequestContext() @@ -16,7 +19,9 @@ public RequestContext() public HeaderCollection Headers { get; private set; } - public string Method { get; private set;} + public string Method { get; private set; } + public EnumRequestType RequestType { get; private set; } + public string HttpVersion { get; private set; } @@ -41,12 +46,28 @@ public void SetHeader(string header) { var splitIndex = header.IndexOf(':'); Headers.AddValue(header.Substring(0, splitIndex), header.Substring(splitIndex + 1).TrimStart(' ')); + } public void SetRequestLineAndUri(string requestLine) { var parts = requestLine.Split(' '); Method = parts[0]; + switch (Method.Trim().ToUpper()) + { + case "POST": + RequestType = EnumRequestType.Post; + break; + case "GET": + RequestType = EnumRequestType.Get; + break; + case "PUT": + RequestType = EnumRequestType.Put; + break; + default: + RequestType = EnumRequestType.Undefined; + break; + } var host = Headers["Host"].Split(':'); if (host.Length > 1) { diff --git a/Rinsen.WebServer/Result.cs b/Rinsen.WebServer/Result.cs index bc65aea..57e069d 100644 --- a/Rinsen.WebServer/Result.cs +++ b/Rinsen.WebServer/Result.cs @@ -5,6 +5,7 @@ namespace Rinsen.WebServer { public class Result { - public string Data { get; set; } + public bool Success { get; set; } + public string Message { get; set; } } } diff --git a/RinsenWebServer.sln b/RinsenWebServer.sln index e37d7d5..36e2d5c 100644 --- a/RinsenWebServer.sln +++ b/RinsenWebServer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 +VisualStudioVersion = 12.0.40629.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoWeb", "DemoWeb\DemoWeb.csproj", "{72EE43DE-970A-443F-851F-CA89A061CC1E}" EndProject @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rinsen.WebServer", "Rinsen. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rinsen.WebServer.UnitTests", "Rinsen.WebServer.UnitTests\Rinsen.WebServer.UnitTests.csproj", "{A08D703F-165F-4605-8DC1-05743A113D9E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetduinoSDCard", "NetduinoSDCard\NetduinoSDCard.csproj", "{66F7DA1E-2931-4E24-81F7-471B08488B96}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,6 +39,12 @@ Global {A08D703F-165F-4605-8DC1-05743A113D9E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A08D703F-165F-4605-8DC1-05743A113D9E}.Release|Any CPU.Build.0 = Release|Any CPU {A08D703F-165F-4605-8DC1-05743A113D9E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {66F7DA1E-2931-4E24-81F7-471B08488B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66F7DA1E-2931-4E24-81F7-471B08488B96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66F7DA1E-2931-4E24-81F7-471B08488B96}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {66F7DA1E-2931-4E24-81F7-471B08488B96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66F7DA1E-2931-4E24-81F7-471B08488B96}.Release|Any CPU.Build.0 = Release|Any CPU + {66F7DA1E-2931-4E24-81F7-471B08488B96}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 81a2023188430b917ccd586888193f4e234494c4 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Tue, 8 Dec 2015 19:48:05 -0600 Subject: [PATCH 05/14] Cleaned Up JsonSerializer Interface leveraged the JsonSerializer class to inherit from Json.NETMF so that JSON serialization implementation could remain unchanged. --- .../Rinsen.WebServer.UnitTests.csproj | 4 + Rinsen.WebServer.UnitTests/packages.config | 1 + Rinsen.WebServer/Controller.cs | 3 +- .../Serializers/JsonSerializer.cs | 138 +----------------- 4 files changed, 7 insertions(+), 139 deletions(-) diff --git a/Rinsen.WebServer.UnitTests/Rinsen.WebServer.UnitTests.csproj b/Rinsen.WebServer.UnitTests/Rinsen.WebServer.UnitTests.csproj index ae4e967..34e5696 100644 --- a/Rinsen.WebServer.UnitTests/Rinsen.WebServer.UnitTests.csproj +++ b/Rinsen.WebServer.UnitTests/Rinsen.WebServer.UnitTests.csproj @@ -48,6 +48,10 @@ + + ..\packages\Json.NetMF.1.3.0.0\lib\netmf43\Json.NetMF.dll + True + ..\packages\MFUnit.0.4\lib\netmf43\MFUnit.dll diff --git a/Rinsen.WebServer.UnitTests/packages.config b/Rinsen.WebServer.UnitTests/packages.config index 9d0705a..8ac6c1b 100644 --- a/Rinsen.WebServer.UnitTests/packages.config +++ b/Rinsen.WebServer.UnitTests/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Rinsen.WebServer/Controller.cs b/Rinsen.WebServer/Controller.cs index 0806aa7..b66cd17 100644 --- a/Rinsen.WebServer/Controller.cs +++ b/Rinsen.WebServer/Controller.cs @@ -31,8 +31,7 @@ public void SetHtmlResult(string data) public void SetJsonResult(object objectToSerialize) { HttpContext.Response.ContentType = "application/json"; - //HttpContext.Response.Data = JsonSerializer.Serialize(objectToSerialize); - HttpContext.Response.Data = Json.NETMF.JsonSerializer.SerializeObject(objectToSerialize); + HttpContext.Response.Data = JsonSerializer.Serialize(objectToSerialize); } /// diff --git a/Rinsen.WebServer/Serializers/JsonSerializer.cs b/Rinsen.WebServer/Serializers/JsonSerializer.cs index 06f075f..fa2e34c 100644 --- a/Rinsen.WebServer/Serializers/JsonSerializer.cs +++ b/Rinsen.WebServer/Serializers/JsonSerializer.cs @@ -5,144 +5,8 @@ namespace Rinsen.WebServer.Serializers { - public class JsonSerializer : IJsonSerializer + public class JsonSerializer : Json.NETMF.JsonSerializer, IJsonSerializer { - public virtual string Serialize(object obj) - { - var jsonStringBuilder = new StringBuilder("{"); - var properties = obj.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); - var first = true; - foreach (var property in properties) - { - // Skip if no return value - if (property.ReturnType == typeof(void)) - continue; - - var propertyName = property.Name.Substring(4); - propertyName = propertyName[0].ToLower() + propertyName.Substring(1); - if (first) - { - jsonStringBuilder.Append("\"" + propertyName + "\": "); - first = false; - } - else - { - jsonStringBuilder.Append(",\"" + propertyName + "\": "); - } - - var value = property.Invoke(obj, new object[] { }); - - if (!AppendValueToJsonBuilder(value, jsonStringBuilder)) - { - // If unknown object in value try to serialize object content and add as value - jsonStringBuilder.Append(Serialize(value)); - } - } - jsonStringBuilder.Append("}"); - - return jsonStringBuilder.ToString(); - } - - private bool AppendValueToJsonBuilder(object value, StringBuilder jsonStringBuilder) - { - var type = value.GetType(); - - if (type == typeof(ArrayList)) - { - SerializeArray((ArrayList)value, jsonStringBuilder); - return true; - } - if (type == typeof(bool)) - { - jsonStringBuilder.Append(value.ToString().ToLower()); - return true; - } - if (type == typeof(byte)) - { - jsonStringBuilder.Append((byte)value); - return true; - } - if (type == typeof(sbyte)) - { - jsonStringBuilder.Append((sbyte)value); - return true; - } - if (type == typeof(char)) - { - jsonStringBuilder.Append("\"" + (char)value + "\""); - return true; - } - if (type == typeof(double)) - { - jsonStringBuilder.Append((double)value); - return true; - } - if (type == typeof(float)) - { - jsonStringBuilder.Append((float)value); - return true; - } - if (type == typeof(int)) - { - jsonStringBuilder.Append((int)value); - return true; - } - if (type == typeof(uint)) - { - jsonStringBuilder.Append((uint)value); - return true; - } - if (type == typeof(long)) - { - jsonStringBuilder.Append((long)value); - return true; - } - if (type == typeof(ulong)) - { - jsonStringBuilder.Append((ulong)value); - return true; - } - if (type == typeof(short)) - { - jsonStringBuilder.Append((short)value); - return true; - } - if (type == typeof(ushort)) - { - jsonStringBuilder.Append((ushort)value); - return true; - } - if (type == typeof(string)) - { - jsonStringBuilder.Append("\"" + (string)value + "\""); - return true; - } - - return false; - } - - private void SerializeArray(ArrayList valueArray, StringBuilder jsonStringBuilder) - { - if (valueArray.Count == 0) - return; - - jsonStringBuilder.Append("["); - var first = true; - foreach (var value in valueArray) - { - if (!first) - jsonStringBuilder.Append(", "); - else - first = false; - - if (!AppendValueToJsonBuilder(value, jsonStringBuilder)) - { - // If unknown object in value try to serialize object content and add as value - jsonStringBuilder.Append(Serialize(value)); - } - } - jsonStringBuilder.Append("]"); - } } } From aca4ff7a34822808906578af1c91a3e032da0609 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Tue, 8 Dec 2015 20:31:05 -0600 Subject: [PATCH 06/14] Added SDCardContents Directory I added an SDCardContents Directory for use in serving up the filemanager.html page that shows an ajax post to upload a text file to the Netduino --- DemoWeb/Program.cs | 6 +- DemoWeb/SDCardManager.cs | 2 +- .../FileAndDirectoryService.cs | 26 ++++++-- .../FileController.cs | 2 +- .../{ISDCard.cs => ISDCardManager.cs} | 2 +- ...en.WebServer.FileAndDirectoryServer.csproj | 2 +- Rinsen.WebServer/Rinsen.WebServer.csproj | 3 - SDCardContens/WWW/filemanager.html | 55 +++++++++++++++++ SDCardContens/WWW/images/img01.jpg | Bin 0 -> 55391 bytes SDCardContens/WWW/images/img02.gif | Bin 0 -> 6927 bytes SDCardContens/WWW/images/img03.jpg | Bin 0 -> 10205 bytes SDCardContens/WWW/js/filemanager.js | 30 ++++++++++ SDCardContens/WWW/style/style.css | 56 ++++++++++++++++++ 13 files changed, 171 insertions(+), 13 deletions(-) rename Rinsen.WebServer.FileAndDirectoryServer/{ISDCard.cs => ISDCardManager.cs} (93%) create mode 100644 SDCardContens/WWW/filemanager.html create mode 100644 SDCardContens/WWW/images/img01.jpg create mode 100644 SDCardContens/WWW/images/img02.gif create mode 100644 SDCardContens/WWW/images/img03.jpg create mode 100644 SDCardContens/WWW/js/filemanager.js create mode 100644 SDCardContens/WWW/style/style.css diff --git a/DemoWeb/Program.cs b/DemoWeb/Program.cs index 9445cb3..1543cf6 100644 --- a/DemoWeb/Program.cs +++ b/DemoWeb/Program.cs @@ -12,9 +12,11 @@ public static void Main() var webServer = new WebServer(); webServer.AddRequestFilter(new RequestFilter()); var fileAndDirectoryService = new FileAndDirectoryService(); - fileAndDirectoryService.SetSDCard(new SDCardManager(WORKINGDIRECTORY)); + fileAndDirectoryService.SetSDCardManager(new SDCardManager(WORKINGDIRECTORY)); webServer.SetFileAndDirectoryService(new FileAndDirectoryService()); - webServer.RouteTable.DefaultControllerName = "Default"; + /*By not setting a default Controller, the root web directory (set with WORKINGDIRECTORY) will have it's contents listed + * with the FileAndDirectoryServer library */ + //webServer.RouteTable.DefaultControllerName = "Default"; webServer.StartServer(); } } diff --git a/DemoWeb/SDCardManager.cs b/DemoWeb/SDCardManager.cs index bc3e883..49b2b00 100644 --- a/DemoWeb/SDCardManager.cs +++ b/DemoWeb/SDCardManager.cs @@ -5,7 +5,7 @@ namespace DemoWeb { - public class SDCardManager : NetduinoSDCard.SDCard, ISDCard + public class SDCardManager : NetduinoSDCard.SDCard, ISDCardManager { public SDCardManager(string WorkingDirectory) : base(WorkingDirectory) {} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs index 96ce0c4..9fa0a37 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs @@ -9,7 +9,7 @@ namespace Rinsen.WebServer.FileAndDirectoryServer { public class FileAndDirectoryService : IFileAndDirectoryService { - private ISDCard SDCardManager { get; set; } + private ISDCardManager SDCardManager { get; set; } public void SetFileNameAndPathIfFileExists(ServerContext serverContext, HttpContext httpContext) { @@ -88,12 +88,30 @@ public bool TryGetDirectoryResultIfDirectoryExists(ServerContext serverContext, public string GetFileServiceBasePath() { - return SDCardManager.GetWorkingDirectoryPath(); + if (HasSDCardManager) + return SDCardManager.GetWorkingDirectoryPath(); + + string basePath = "\\SD\\WWW"; + var directory = new DirectoryInfo(basePath); + if (directory.Exists) + { + return basePath; + } + + basePath = "\\WINFS\\WWW"; + directory = new DirectoryInfo(basePath); + if (directory.Exists) + { + return basePath; + } + return string.Empty; } - public void SetSDCard(ISDCard sdCard) + public void SetSDCardManager(ISDCardManager sdCardManager) { - SDCardManager = sdCard; + SDCardManager = sdCardManager; } + + public bool HasSDCardManager { get { return SDCardManager != null; } } } } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index 553ea9a..fee0250 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -8,7 +8,7 @@ namespace Rinsen.WebServer.FileAndDirectoryServer { public class FileController : Controller { - public ISDCard SDCardManager { get; set; } + public ISDCardManager SDCardManager { get; set; } public string RecieveFile() { diff --git a/Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs b/Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs similarity index 93% rename from Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs rename to Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs index 9297d9b..09a74e5 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/ISDCard.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs @@ -6,7 +6,7 @@ namespace Rinsen.WebServer.FileAndDirectoryServer { - public interface ISDCard + public interface ISDCardManager { string GetWorkingDirectoryPath(); diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index ae5527c..da704d1 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -36,7 +36,7 @@ - + diff --git a/Rinsen.WebServer/Rinsen.WebServer.csproj b/Rinsen.WebServer/Rinsen.WebServer.csproj index 6be6c57..05c47e5 100644 --- a/Rinsen.WebServer/Rinsen.WebServer.csproj +++ b/Rinsen.WebServer/Rinsen.WebServer.csproj @@ -89,7 +89,4 @@ - - - \ No newline at end of file diff --git a/SDCardContens/WWW/filemanager.html b/SDCardContens/WWW/filemanager.html new file mode 100644 index 0000000..bb4959d --- /dev/null +++ b/SDCardContens/WWW/filemanager.html @@ -0,0 +1,55 @@ + + + + + + Cart Scale File Manager + + + + + + + + + +
+
+ +

Cart Scale File Manager

+
+
+
+
+
+ Network Information +
+ + +
+ +
+ +
+
+ +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/SDCardContens/WWW/images/img01.jpg b/SDCardContens/WWW/images/img01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..985a40ab85afa008796705c7496640862df85cba GIT binary patch literal 55391 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 literal 0 HcmV?d00001 diff --git a/SDCardContens/WWW/images/img02.gif b/SDCardContens/WWW/images/img02.gif new file mode 100644 index 0000000000000000000000000000000000000000..070011a63b348bf0c76f948f510cc9c5d4329b31 GIT binary patch literal 6927 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 I066@A0Dx$W*#H0l literal 0 HcmV?d00001 diff --git a/SDCardContens/WWW/images/img03.jpg b/SDCardContens/WWW/images/img03.jpg new file mode 100644 index 0000000000000000000000000000000000000000..916a157da04df99cac1aa4816b7139d07c112892 GIT binary patch literal 10205 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 AFaQ7m literal 0 HcmV?d00001 diff --git a/SDCardContens/WWW/js/filemanager.js b/SDCardContens/WWW/js/filemanager.js new file mode 100644 index 0000000..79e0d03 --- /dev/null +++ b/SDCardContens/WWW/js/filemanager.js @@ -0,0 +1,30 @@ +var intMessageFadeTimeout = 10000; +var sFileUploadURL = 'Files/Upload'; + + +//Prevent forms from submitting so that I can use the jquery button click method with an ajax call instead... +$(".preventsubmit").submit(function (event) { + event.preventDefault(); +}); + +$("#btnUpload").button().click(function () { + + //var data = new FormData(); + //jQuery.each(jQuery('#file')[0].files, function (i, file) { + // data.append('file-' + i, file); + //}); + var formData = new FormData($("#frmFileUpload")[0]); + + $.ajax({ + url: sFileUploadURL, + type: 'POST', + data: formData, + async: false, + cache: false, + contentType: false, + processData: false, + success: function (returndata) { + alert(returndata); + } + }); +}); \ No newline at end of file diff --git a/SDCardContens/WWW/style/style.css b/SDCardContens/WWW/style/style.css new file mode 100644 index 0000000..ef6b25c --- /dev/null +++ b/SDCardContens/WWW/style/style.css @@ -0,0 +1,56 @@ +body { + background: #0C1114 url(/images/img01.jpg) no-repeat top center; + color: #8EA2AD; + line-height: 1.75em; + font-size: 12pt; +} + +input.form-submit { + color: #FFFFFF; + font-family: Oswald, sans-serif; + padding: 5px; + margin-left: 1em; + background: #CC7F5C; + border: 0; +} + +input.form-text { + padding: 10px; + border: dotted 1px #26333D; +} + +h1.Title { + color: cornflowerblue; +} + +span.Messages { + color: red; +} + +.float-left { + float: left; +} + +.float-right { + float: right; +} + +.clear-fix { + clear: both; +} + +.clear-left { + clear: left; +} + +.clear-right { + clear: right; +} + +input.stretch { + width: 100%; +} + +input.stretch3Quarters { + width: 75%; +} \ No newline at end of file From 21013973d2e67c885d20712740009e007f25ca72 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Tue, 8 Dec 2015 22:36:41 -0600 Subject: [PATCH 07/14] Made ContentType Search Case insensitive Used the .ToUpper() method to perform case insensitive matches on content types base on file extensions in the FileAndDirectoryService Class. Replaced Method text matches with enum implementation of the value. --- .../FileAndDirectoryService.cs | 14 +++++++------- .../FileController.cs | 8 ++++++-- Rinsen.WebServer/Controller.cs | 4 ++-- Rinsen.WebServer/Extensions/ExtensionMethods.cs | 8 ++++++++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs index 9fa0a37..4627a3e 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs @@ -17,25 +17,25 @@ public void SetFileNameAndPathIfFileExists(ServerContext serverContext, HttpCont string contentType = string.Empty; - if (fileFullName.IndexOf(".htm") != -1 || fileFullName.IndexOf(".HTM") != -1 || fileFullName.IndexOf(".html") != -1 || fileFullName.IndexOf(".HTML") != -1) + if (fileFullName.ToUpper().IndexOf(".HTM") != -1 || fileFullName.ToUpper().IndexOf(".HTML") != -1) { contentType = "text/html"; } - else if (fileFullName.IndexOf(".css") != -1 || fileFullName.IndexOf(".CSS") != -1) + else if (fileFullName.ToUpper().IndexOf(".CSS") != -1) { contentType = "text/css"; } - else if (fileFullName.IndexOf(".txt") != -1 || fileFullName.IndexOf(".TXT") != -1) + else if (fileFullName.ToUpper().IndexOf(".TXT") != -1) { contentType = "text/plain"; } - else if (fileFullName.IndexOf(".jpg") != -1 || fileFullName.IndexOf(".JPG") != -1 || - fileFullName.IndexOf(".bmp") != -1 || fileFullName.IndexOf(".BMP") != -1 || - fileFullName.IndexOf(".jpeg") != -1 || fileFullName.IndexOf(".JPEG") != -1) + else if (fileFullName.ToUpper().IndexOf(".JPG") != -1 || + fileFullName.ToUpper().IndexOf(".BMP") != -1 || + fileFullName.ToUpper().IndexOf(".JPEG") != -1) { contentType = "image/jpeg"; } - else if (fileFullName.IndexOf(".js") != -1 || fileFullName.IndexOf(".JS") != -1) + else if (fileFullName.ToUpper().IndexOf(".JS") != -1) { contentType = "text/javascript"; } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index fee0250..bc78ce4 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -4,6 +4,9 @@ using System.Collections; using System.Text.RegularExpressions; using System.Text; +using Rinsen.WebServer.Collections; + + namespace Rinsen.WebServer.FileAndDirectoryServer { public class FileController : Controller @@ -38,7 +41,7 @@ public string RecieveFile() var ContentType = request.Headers["Content-Type"]; //will have a value like "multipart/form-data; boundary=---------------------------2261521032598" var boundarystring = ContentType.Split(new char[] { ';' })[1]; //gives me " boundary=---------------------------2261521032598" string boundary = boundarystring.Split(new char[] { '=' })[1].ToString(); //gives me "---------------------------2261521032598" - // int nextBoundaryIndex = requestContent.IndexOf(boundary);// todo boundaries can change + //int nextBoundaryIndex = requestContent.IndexOf(boundary);// todo boundaries can change boundaryPattern = "--" + boundary;//"#\n\n(.*)\n--$boundary#" Regex MyRegex = new Regex(boundaryPattern, RegexOptions.Multiline); string[] split = MyRegex.Split(requestContent); @@ -49,7 +52,7 @@ public string RecieveFile() if (pos >= 0) { string remainder = split[i].Substring(pos + _ContentDispositionSearch.Length); - // ConsoleWrite.Print(remainder); + //ConsoleWrite.Print(remainder); string[] nameSplit = remainder.Split(new char[] { '\"' }, 2); string name = nameSplit[0]; if (nameSplit[1][0] == ';') @@ -117,5 +120,6 @@ public string RecieveFile() return message; } + } } diff --git a/Rinsen.WebServer/Controller.cs b/Rinsen.WebServer/Controller.cs index b66cd17..6d5d41b 100644 --- a/Rinsen.WebServer/Controller.cs +++ b/Rinsen.WebServer/Controller.cs @@ -40,11 +40,11 @@ public void SetJsonResult(object objectToSerialize) /// public FormCollection GetFormCollection() { - if (HttpContext.Request.Method == "GET") + if (HttpContext.Request.RequestType == EnumRequestType.Get) { return new FormCollection(HttpContext.Request.Uri.QueryString); } - else if (HttpContext.Request.Method == "POST") + else if (HttpContext.Request.RequestType == EnumRequestType.Post) { var socket = HttpContext.Socket; var buffer = new byte[2048]; diff --git a/Rinsen.WebServer/Extensions/ExtensionMethods.cs b/Rinsen.WebServer/Extensions/ExtensionMethods.cs index b8f665d..26c31b6 100644 --- a/Rinsen.WebServer/Extensions/ExtensionMethods.cs +++ b/Rinsen.WebServer/Extensions/ExtensionMethods.cs @@ -57,6 +57,14 @@ public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUn } } + public static byte[] GetMoreBytes(this Socket socket, int rxBufferSize, int count) + { + byte[] result = new byte[rxBufferSize]; + SocketFlags socketFlags = new SocketFlags(); + count = socket.Receive(result, result.Length, socketFlags); + return result; + } + public static int Count(this IEnumerable collection) { var count = 0; From 1c24eb18357682d3fdb4f0a3bed18624ab56c86d Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Wed, 9 Dec 2015 06:19:33 -0600 Subject: [PATCH 08/14] Moved GetMoreBytes to an extension method I moved the function GetMoreBytes to an extension method instead of in the SDCardMagager Class --- NetduinoSDCard/SDCard.cs | 9 ------ .../ExtensionMethods.cs | 28 +++++++++++++++++ .../FileController.cs | 31 +++++++++++++++++-- .../ISDCardManager.cs | 2 -- ...en.WebServer.FileAndDirectoryServer.csproj | 1 + .../Extensions/ExtensionMethods.cs | 8 ----- 6 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs diff --git a/NetduinoSDCard/SDCard.cs b/NetduinoSDCard/SDCard.cs index 6d7f28f..6827446 100644 --- a/NetduinoSDCard/SDCard.cs +++ b/NetduinoSDCard/SDCard.cs @@ -204,15 +204,6 @@ public void SendFile(string fullPath, Socket socket) ConsoleWrite.Print("Failed to read chunk, full path: " + fullPath); } - const int _PostRxBufferSize = 1500; - public byte[] GetMoreBytes(Socket connectionSocket, out int count) - { - byte[] result = new byte[_PostRxBufferSize]; - SocketFlags socketFlags = new SocketFlags(); - count = connectionSocket.Receive(result, result.Length, socketFlags); - return result; - } - public static string Replace(string input, char[] oldText, string newText) { string result = ""; diff --git a/Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs b/Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs new file mode 100644 index 0000000..0827791 --- /dev/null +++ b/Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.SPOT; + +using System.Net.Sockets; + + +namespace Rinsen.WebServer.FileAndDirectoryServer +{ + public static class ExtensionMethods + { + public static byte[] GetMoreBytes(this Socket socket, int rxBufferSize, out int count) + { + byte[] result = new byte[rxBufferSize]; + SocketFlags socketFlags = new SocketFlags(); + count = socket.Receive(result, result.Length, socketFlags); + return result; + } + + //const int _PostRxBufferSize = 1500; + //public byte[] GetMoreBytes(Socket connectionSocket, out int count) + //{ + // byte[] result = new byte[_PostRxBufferSize]; + // SocketFlags socketFlags = new SocketFlags(); + // count = connectionSocket.Receive(result, result.Length, socketFlags); + // return result; + //} + } +} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index bc78ce4..0cbb349 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -12,6 +12,7 @@ namespace Rinsen.WebServer.FileAndDirectoryServer public class FileController : Controller { public ISDCardManager SDCardManager { get; set; } + private const int _PostRxBufferSize = 1500; public string RecieveFile() { @@ -32,7 +33,7 @@ public string RecieveFile() if (contentLengthReceived < contentLengthFromHeader)// get next packet, this should have the start of any file in it. // todo put timeout { int count = 0; - byte[] data = SDCardManager.GetMoreBytes(HttpContext.Socket, out count); + byte[] data = HttpContext.Socket.GetMoreBytes(_PostRxBufferSize, out count); requestContent += new string(Encoding.UTF8.GetChars(data, contentLengthReceived, count)); contentLengthReceived += count; } @@ -93,7 +94,7 @@ public string RecieveFile() byte[] data = null; int count = 0; { - data = SDCardManager.GetMoreBytes(HttpContext.Socket, out count); + data = HttpContext.Socket.GetMoreBytes(_PostRxBufferSize, out count); contentLengthReceived += count; //requestContent = new string(Encoding.UTF8.GetChars(data, 0, count)); } @@ -121,5 +122,31 @@ public string RecieveFile() return message; } + + //public override FormCollection GetFormCollection() + //{ + // if (HttpContext.Request.RequestType == EnumRequestType.Get) + // { + // return new FormCollection(HttpContext.Request.Uri.QueryString); + // } + // else if (HttpContext.Request.RequestType == EnumRequestType.Post) + // { + // var socket = HttpContext.Socket; + // var buffer = new byte[2048]; + + // var formCollection = new FormCollection(HttpContext.Request.Uri.QueryString); + + // while (socket.Available > 0) + // { + // socket.ReceiveUntil(buffer, "&"); + // var keyValuePair = new String(Encoding.UTF8.GetChars(buffer)).Split('='); + // formCollection.AddValue(keyValuePair[0], keyValuePair[1]); + // } + + // return formCollection; + // } + + // throw new NotSupportedException("Only GET and POST is supported"); + //} } } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs b/Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs index 09a74e5..5618fde 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/ISDCardManager.cs @@ -12,8 +12,6 @@ public interface ISDCardManager void SendFile(string fullPath, Socket socket); - byte[] GetMoreBytes(Socket connectionSocket, out int count); - bool Write(string path, string fileName, FileMode fileMode, string text); bool Write(string path, string fileName, FileMode fileMode, byte[] bytes, int length); diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index da704d1..efd9a69 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -35,6 +35,7 @@ + diff --git a/Rinsen.WebServer/Extensions/ExtensionMethods.cs b/Rinsen.WebServer/Extensions/ExtensionMethods.cs index 26c31b6..b8f665d 100644 --- a/Rinsen.WebServer/Extensions/ExtensionMethods.cs +++ b/Rinsen.WebServer/Extensions/ExtensionMethods.cs @@ -57,14 +57,6 @@ public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUn } } - public static byte[] GetMoreBytes(this Socket socket, int rxBufferSize, int count) - { - byte[] result = new byte[rxBufferSize]; - SocketFlags socketFlags = new SocketFlags(); - count = socket.Receive(result, result.Length, socketFlags); - return result; - } - public static int Count(this IEnumerable collection) { var count = 0; From 7fd091511ddf62ff6c14aa37f4eded88d41879ae Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Wed, 9 Dec 2015 19:15:50 -0600 Subject: [PATCH 09/14] Implemented ContentType Enums Implemented ContentType Enums, ContentSubtype enums. Fixed issues with my SDCard class needing to be static and moved extension methods all into one File on Rinsen.WebServer --- DemoWeb/FilesController.cs | 7 +- DemoWeb/Program.cs | 2 +- .../ExtensionMethods.cs | 28 ---- .../FileAndDirectoryService.cs | 40 +++-- .../FileController.cs | 33 +--- ...en.WebServer.FileAndDirectoryServer.csproj | 1 - Rinsen.WebServer/ContentType.cs | 46 ++++++ Rinsen.WebServer/Controller.cs | 8 +- .../Extensions/ExtensionMethods.cs | 151 ++++++++++++++++++ Rinsen.WebServer/RequestContext.cs | 54 +++++-- Rinsen.WebServer/ResponseContext.cs | 2 +- Rinsen.WebServer/ResponseHeaderBuilder.cs | 2 +- Rinsen.WebServer/Rinsen.WebServer.csproj | 1 + 13 files changed, 278 insertions(+), 97 deletions(-) delete mode 100644 Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs create mode 100644 Rinsen.WebServer/ContentType.cs diff --git a/DemoWeb/FilesController.cs b/DemoWeb/FilesController.cs index adb0071..727afa2 100644 --- a/DemoWeb/FilesController.cs +++ b/DemoWeb/FilesController.cs @@ -31,10 +31,11 @@ public void Upload() { this.SDCardManager = new SDCardManager(Program.WORKINGDIRECTORY); - if (HttpContext.Request.RequestType == EnumRequestType.Post) + if (HttpContext.Request.Method == HTTPMethod.Post) { - var doFileUpload = RecieveFile(); - SetJsonResult(new Result { Success = true, Message = doFileUpload }); + var doFileUpload = RecieveFiles(); + + SetJsonResult(new Result { Success = true, Message = "Files Recieved!" }); } } } diff --git a/DemoWeb/Program.cs b/DemoWeb/Program.cs index 1543cf6..6cee87a 100644 --- a/DemoWeb/Program.cs +++ b/DemoWeb/Program.cs @@ -17,7 +17,7 @@ public static void Main() /*By not setting a default Controller, the root web directory (set with WORKINGDIRECTORY) will have it's contents listed * with the FileAndDirectoryServer library */ //webServer.RouteTable.DefaultControllerName = "Default"; - webServer.StartServer(); + webServer.StartServer(80); } } } \ No newline at end of file diff --git a/Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs b/Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs deleted file mode 100644 index 0827791..0000000 --- a/Rinsen.WebServer.FileAndDirectoryServer/ExtensionMethods.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Microsoft.SPOT; - -using System.Net.Sockets; - - -namespace Rinsen.WebServer.FileAndDirectoryServer -{ - public static class ExtensionMethods - { - public static byte[] GetMoreBytes(this Socket socket, int rxBufferSize, out int count) - { - byte[] result = new byte[rxBufferSize]; - SocketFlags socketFlags = new SocketFlags(); - count = socket.Receive(result, result.Length, socketFlags); - return result; - } - - //const int _PostRxBufferSize = 1500; - //public byte[] GetMoreBytes(Socket connectionSocket, out int count) - //{ - // byte[] result = new byte[_PostRxBufferSize]; - // SocketFlags socketFlags = new SocketFlags(); - // count = connectionSocket.Receive(result, result.Length, socketFlags); - // return result; - //} - } -} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs index 4627a3e..31fe430 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs @@ -3,52 +3,54 @@ using System.Net.Sockets; using System.Collections; - +using Microsoft.SPOT; namespace Rinsen.WebServer.FileAndDirectoryServer { public class FileAndDirectoryService : IFileAndDirectoryService { - private ISDCardManager SDCardManager { get; set; } + private static ISDCardManager SDCardManager { get; set; } public void SetFileNameAndPathIfFileExists(ServerContext serverContext, HttpContext httpContext) { + var contentType = httpContext.Response.ContentType; var fileFullName = serverContext.FileServerBasePath + httpContext.Request.Uri.LocalPath; - string contentType = string.Empty; - if (fileFullName.ToUpper().IndexOf(".HTM") != -1 || fileFullName.ToUpper().IndexOf(".HTML") != -1) { - contentType = "text/html"; + contentType = new ContentType { MainContentType = EnumMainContentType.Text, SubContentType = EnumSubContentType.Html }; } else if (fileFullName.ToUpper().IndexOf(".CSS") != -1) { - contentType = "text/css"; + contentType = new ContentType { MainContentType = EnumMainContentType.Text, SubContentType = EnumSubContentType.Css }; } else if (fileFullName.ToUpper().IndexOf(".TXT") != -1) { - contentType = "text/plain"; + contentType = new ContentType { MainContentType = EnumMainContentType.Text, SubContentType = EnumSubContentType.Plain }; } else if (fileFullName.ToUpper().IndexOf(".JPG") != -1 || fileFullName.ToUpper().IndexOf(".BMP") != -1 || fileFullName.ToUpper().IndexOf(".JPEG") != -1) { - contentType = "image/jpeg"; + contentType = new ContentType { MainContentType = EnumMainContentType.Image, SubContentType = EnumSubContentType.Jpeg }; } else if (fileFullName.ToUpper().IndexOf(".JS") != -1) { - contentType = "text/javascript"; + //note this was text/javascript; I updated because it was obsoleted in favor of application/javascript + contentType = new ContentType { MainContentType = EnumMainContentType.Application, SubContentType = EnumSubContentType.JavaScript }; } - if (contentType != string.Empty) + if (contentType != null) { + Debug.Print("Set Content Type: " + contentType); httpContext.Response.ContentType = contentType; } else { + Debug.Print("No Matching Content Type set... " + contentType); return; } - + if (File.Exists(fileFullName)) { var files = new DirectoryInfo(fileFullName.Substring(0, fileFullName.LastIndexOf('\\'))).GetFiles(); @@ -79,7 +81,7 @@ public bool TryGetDirectoryResultIfDirectoryExists(ServerContext serverContext, DirectoryInfo[] directories = rootDirectory.GetDirectories(); FileInfo[] files = rootDirectory.GetFiles(); httpContext.Response.Data = new DirectoryListBuilder().GenerateSimpleDirectoryList(httpContext.Request.Uri.RawPath, directories, files, serverContext.HostName); - httpContext.Response.ContentType = "text/html"; + httpContext.Response.ContentType = new ContentType { MainContentType = EnumMainContentType.Text, SubContentType = EnumSubContentType.Html }; return true; } @@ -88,13 +90,21 @@ public bool TryGetDirectoryResultIfDirectoryExists(ServerContext serverContext, public string GetFileServiceBasePath() { + string basePath = string.Empty; + + Debug.Print("Setting File Services Base Path..." + "\r\nHas an SDCard Manager: " + HasSDCardManager); if (HasSDCardManager) - return SDCardManager.GetWorkingDirectoryPath(); + { + basePath = SDCardManager.GetWorkingDirectoryPath(); + Debug.Print("Base path is: " + basePath); + return basePath; + } - string basePath = "\\SD\\WWW"; + basePath = "\\SD\\WWW"; var directory = new DirectoryInfo(basePath); if (directory.Exists) { + Microsoft.SPOT.Debug.Print("Base path is: " + basePath); return basePath; } @@ -102,8 +112,10 @@ public string GetFileServiceBasePath() directory = new DirectoryInfo(basePath); if (directory.Exists) { + Debug.Print("Base path is: " + basePath); return basePath; } + Debug.Print("No Base Path Set..."); return string.Empty; } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index 0cbb349..c795835 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using System.Text; using Rinsen.WebServer.Collections; +using Rinsen.WebServer.Extensions; namespace Rinsen.WebServer.FileAndDirectoryServer @@ -14,11 +15,11 @@ public class FileController : Controller public ISDCardManager SDCardManager { get; set; } private const int _PostRxBufferSize = 1500; - public string RecieveFile() + public string RecieveFiles() { var request = HttpContext.Request; Hashtable formVariables = new Hashtable(); - if (request.RequestType == EnumRequestType.Post) + if (request.Method == HTTPMethod.Post) { // This form should be short so get it all data now, in one go. @@ -38,7 +39,6 @@ public string RecieveFile() contentLengthReceived += count; } - string strTemp = request.Headers["Content-Type"].Split(new char[] { ';' })[1].Split(new char[] { '=' })[1].ToString(); var ContentType = request.Headers["Content-Type"]; //will have a value like "multipart/form-data; boundary=---------------------------2261521032598" var boundarystring = ContentType.Split(new char[] { ';' })[1]; //gives me " boundary=---------------------------2261521032598" string boundary = boundarystring.Split(new char[] { '=' })[1].ToString(); //gives me "---------------------------2261521032598" @@ -121,32 +121,5 @@ public string RecieveFile() return message; } - - - //public override FormCollection GetFormCollection() - //{ - // if (HttpContext.Request.RequestType == EnumRequestType.Get) - // { - // return new FormCollection(HttpContext.Request.Uri.QueryString); - // } - // else if (HttpContext.Request.RequestType == EnumRequestType.Post) - // { - // var socket = HttpContext.Socket; - // var buffer = new byte[2048]; - - // var formCollection = new FormCollection(HttpContext.Request.Uri.QueryString); - - // while (socket.Available > 0) - // { - // socket.ReceiveUntil(buffer, "&"); - // var keyValuePair = new String(Encoding.UTF8.GetChars(buffer)).Split('='); - // formCollection.AddValue(keyValuePair[0], keyValuePair[1]); - // } - - // return formCollection; - // } - - // throw new NotSupportedException("Only GET and POST is supported"); - //} } } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index efd9a69..da704d1 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -35,7 +35,6 @@ - diff --git a/Rinsen.WebServer/ContentType.cs b/Rinsen.WebServer/ContentType.cs new file mode 100644 index 0000000..a270af3 --- /dev/null +++ b/Rinsen.WebServer/ContentType.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.SPOT; + +using Rinsen.WebServer.Extensions; + + +namespace Rinsen.WebServer +{ + public enum EnumMainContentType + { + Application, + Audio, + Example, + Image, + Message, + Model, + MultiPart, + Text, + Video, + Undefined + } + + public enum EnumSubContentType + { + FormData, + Json, + Html, + Css, + Plain, + Jpeg, + JavaScript, + Undefined + } + + //per http://www.iana.org/assignments/media-types/media-types.xhtml + public class ContentType + { + public EnumMainContentType MainContentType { get; set; } + public EnumSubContentType SubContentType { get; set; } + + public override string ToString() + { + return MainContentType.GetName() + "/" + SubContentType.GetName(); + } + } +} diff --git a/Rinsen.WebServer/Controller.cs b/Rinsen.WebServer/Controller.cs index 6d5d41b..aa0c864 100644 --- a/Rinsen.WebServer/Controller.cs +++ b/Rinsen.WebServer/Controller.cs @@ -24,13 +24,13 @@ public void InitializeController(HttpContext httpContext, IJsonSerializer jsonSe public void SetHtmlResult(string data) { - HttpContext.Response.ContentType = "text/html"; + HttpContext.Response.ContentType = new ContentType { MainContentType = EnumMainContentType.Text, SubContentType = EnumSubContentType.Html }; HttpContext.Response.Data = data; } public void SetJsonResult(object objectToSerialize) { - HttpContext.Response.ContentType = "application/json"; + HttpContext.Response.ContentType = new ContentType { MainContentType = EnumMainContentType.Application, SubContentType = EnumSubContentType.Json }; HttpContext.Response.Data = JsonSerializer.Serialize(objectToSerialize); } @@ -40,11 +40,11 @@ public void SetJsonResult(object objectToSerialize) /// public FormCollection GetFormCollection() { - if (HttpContext.Request.RequestType == EnumRequestType.Get) + if (HttpContext.Request.Method == HTTPMethod.Get) { return new FormCollection(HttpContext.Request.Uri.QueryString); } - else if (HttpContext.Request.RequestType == EnumRequestType.Post) + else if (HttpContext.Request.Method == HTTPMethod.Post) { var socket = HttpContext.Socket; var buffer = new byte[2048]; diff --git a/Rinsen.WebServer/Extensions/ExtensionMethods.cs b/Rinsen.WebServer/Extensions/ExtensionMethods.cs index b8f665d..10e94cb 100644 --- a/Rinsen.WebServer/Extensions/ExtensionMethods.cs +++ b/Rinsen.WebServer/Extensions/ExtensionMethods.cs @@ -57,6 +57,42 @@ public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUn } } + public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUntil, out int bytesRecieved) + { + // Clear buffer always to remove old junk if buffer is reused + Array.Clear(buffer, 0, buffer.Length); + var count = 0; + var matchCounter = 0; + + bytesRecieved = 0; + while (socket.Available > 0 && count < buffer.Length) + { + bytesRecieved += socket.Receive(buffer, count, 1, SocketFlags.None); + if (buffer[count] == readUntil[matchCounter]) + { + matchCounter++; + if (matchCounter == readUntil.Length) + { + Array.Clear(buffer, count - readUntil.Length + 1, readUntil.Length); + break; + } + } + else + { + matchCounter = 0; + } + count++; + } + } + + public static byte[] GetMoreBytes(this Socket socket, int rxBufferSize, out int bytesRecieved) + { + byte[] result = new byte[rxBufferSize]; + SocketFlags socketFlags = new SocketFlags(); + bytesRecieved = socket.Receive(result, result.Length, socketFlags); + return result; + } + public static int Count(this IEnumerable collection) { var count = 0; @@ -87,5 +123,120 @@ public static string ToHexString(this byte[] value, int index, int length) } return new string(c, 0, c.Length - 1); } + + public static string GetName(this HTTPMethod source) + { + switch (source) + { + case HTTPMethod.Get: + return "GET"; + case HTTPMethod.Post: + return "POST"; + case HTTPMethod.Put: + return "PUT"; + case HTTPMethod.Delete: + return "DELETE"; + default: + return "Undefined"; + } + } + + public static string GetName(this EnumMainContentType source) + { + switch (source) + { + case EnumMainContentType.Application: + return "application"; + case EnumMainContentType.Audio: + return "audio"; + case EnumMainContentType.Example: + return "example"; + case EnumMainContentType.Image: + return "image"; + case EnumMainContentType.Message: + return "message"; + case EnumMainContentType.Model: + return "model"; + case EnumMainContentType.MultiPart: + return "multipart"; + case EnumMainContentType.Text: + return "text"; + case EnumMainContentType.Video: + return "video"; + default: + return "Undefined"; + } + } + + public static EnumMainContentType GetContentTypeMain(this string source) + { + switch (source.Trim().ToLower()) + { + case "application": + return EnumMainContentType.Application; + case "audio": + return EnumMainContentType.Audio; + case "example": + return EnumMainContentType.Example; + case "image": + return EnumMainContentType.Image; + case "message": + return EnumMainContentType.Message; + case "model": + return EnumMainContentType.Model; + case "multipart": + return EnumMainContentType.MultiPart; + case "text": + return EnumMainContentType.Text; + case "video": + return EnumMainContentType.Video; + default: + return EnumMainContentType.Undefined; + } + } + + public static string GetName(this EnumSubContentType source) + { + switch (source) + { + case EnumSubContentType.FormData: + return "form-data"; + case EnumSubContentType.Json: + return "json"; + case EnumSubContentType.Html: + return "html"; + case EnumSubContentType.Css: + return "css"; + case EnumSubContentType.Plain: + return "plain"; + case EnumSubContentType.Jpeg: + return "jpeg"; + case EnumSubContentType.JavaScript: + return "javascript"; + default: + return "Undefined"; + } + } + + public static EnumSubContentType GetContentTypeSub(this string source) + { + switch (source.Trim().ToLower()) + { + case "form-data": + return EnumSubContentType.FormData; + case "json": + return EnumSubContentType.Json; + case "html": + return EnumSubContentType.Html; + case "css": + return EnumSubContentType.Css; + case "plain": + return EnumSubContentType.Plain; + case "javascript": + return EnumSubContentType.JavaScript; + default: + return EnumSubContentType.Undefined; + } + } } } diff --git a/Rinsen.WebServer/RequestContext.cs b/Rinsen.WebServer/RequestContext.cs index 68584ea..723d1d2 100644 --- a/Rinsen.WebServer/RequestContext.cs +++ b/Rinsen.WebServer/RequestContext.cs @@ -2,11 +2,11 @@ using System.Collections; using Rinsen.WebServer.Routing; using Rinsen.WebServer.Collections; - +using Rinsen.WebServer.Extensions; namespace Rinsen.WebServer { - public enum EnumRequestType { Get, Post, Put, Undefined } //add as neccessary - //public enum EnumContentType { Text, Binary, MultipartFormData, Undefined } //add as neccessary + public enum HTTPMethod { Get, Post, Put, Delete, Undefined } //add as neccessary + public class RequestContext { @@ -15,13 +15,15 @@ public RequestContext() Headers = new HeaderCollection(); } - public string RequestLine { get { return Method + " " + Uri.RawPath + " " + HttpVersion; } } + public string RequestLine { get { return Method.GetName() + " " + Uri.RawPath + " " + HttpVersion; } } public HeaderCollection Headers { get; private set; } - public string Method { get; private set; } - public EnumRequestType RequestType { get; private set; } + public HTTPMethod Method { get; private set; } + + public ContentType ContentType { get; private set; } + public string Boundary { get; set; } public string HttpVersion { get; private set; } @@ -31,7 +33,7 @@ public RequestContext() public RequestRoute RequestedRoute { get; set; } - public string Information { get; set; } + public string Data { get; set; } public void SetHeaders(ArrayList headers) { @@ -45,27 +47,51 @@ public void SetHeaders(ArrayList headers) public void SetHeader(string header) { var splitIndex = header.IndexOf(':'); - Headers.AddValue(header.Substring(0, splitIndex), header.Substring(splitIndex + 1).TrimStart(' ')); + var headerKey = header.Substring(0, splitIndex); + var headerValue = header.Substring(splitIndex + 1).TrimStart(' '); + + if (headerKey.Trim().ToLower().Equals("content-type")) + { + ContentType = new ContentType(); + var contentTypes = headerValue.Split('/'); //sample data "multipart/form-data; boundary=---------------------------2261521032598" + + ContentType.MainContentType = contentTypes[0].GetContentTypeMain(); + if (ContentType.MainContentType == EnumMainContentType.MultiPart) + { + var subcontentData = contentTypes[1].Split(';'); + ContentType.SubContentType = subcontentData[0].GetContentTypeSub(); + var boundaryData = subcontentData[1].Split('='); + Boundary = boundaryData[1].Trim('-'); + } + else + { + ContentType.SubContentType = contentTypes[1].GetContentTypeSub(); + } + } + Headers.AddValue(headerKey, headerValue); } public void SetRequestLineAndUri(string requestLine) { var parts = requestLine.Split(' '); - Method = parts[0]; - switch (Method.Trim().ToUpper()) + + switch (parts[0].Trim().ToUpper()) { case "POST": - RequestType = EnumRequestType.Post; + Method = HTTPMethod.Post; break; case "GET": - RequestType = EnumRequestType.Get; + Method = HTTPMethod.Get; break; case "PUT": - RequestType = EnumRequestType.Put; + Method = HTTPMethod.Put; + break; + case "DELETE": + Method = HTTPMethod.Delete; break; default: - RequestType = EnumRequestType.Undefined; + Method = HTTPMethod.Undefined; break; } var host = Headers["Host"].Split(':'); diff --git a/Rinsen.WebServer/ResponseContext.cs b/Rinsen.WebServer/ResponseContext.cs index aa66bad..bde2100 100644 --- a/Rinsen.WebServer/ResponseContext.cs +++ b/Rinsen.WebServer/ResponseContext.cs @@ -37,7 +37,7 @@ public string DataLength } } - public string ContentType { get; set; } + public ContentType ContentType { get; set; } public HttpStatusCode HttpStatusCode { get; set; } diff --git a/Rinsen.WebServer/ResponseHeaderBuilder.cs b/Rinsen.WebServer/ResponseHeaderBuilder.cs index e438d00..fa162d3 100644 --- a/Rinsen.WebServer/ResponseHeaderBuilder.cs +++ b/Rinsen.WebServer/ResponseHeaderBuilder.cs @@ -22,7 +22,7 @@ public string BuildResponseLineAndHeaders(ResponseContext responseContext) private void SetContentLength(ResponseContext response) { response.Headers["Content-Length"] = response.DataLength; - response.Headers["ContentType"] = response.ContentType; + response.Headers["ContentType"] = response.ContentType.ToString(); } } } diff --git a/Rinsen.WebServer/Rinsen.WebServer.csproj b/Rinsen.WebServer/Rinsen.WebServer.csproj index 05c47e5..35e182b 100644 --- a/Rinsen.WebServer/Rinsen.WebServer.csproj +++ b/Rinsen.WebServer/Rinsen.WebServer.csproj @@ -36,6 +36,7 @@ + From dce589bafb38f86c697dcecac73e23dc77666123 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Thu, 10 Dec 2015 05:33:46 -0600 Subject: [PATCH 10/14] Post Data written to File Ajax post Data is now written to file with no gaps in data. --- .../FileController.cs | 159 ++++++++---------- ...en.WebServer.FileAndDirectoryServer.csproj | 1 + .../Extensions/ExtensionMethods.cs | 54 +++--- 3 files changed, 100 insertions(+), 114 deletions(-) diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index c795835..47b452f 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -15,111 +15,90 @@ public class FileController : Controller public ISDCardManager SDCardManager { get; set; } private const int _PostRxBufferSize = 1500; - public string RecieveFiles() + public FormCollection RecieveFiles() { var request = HttpContext.Request; - Hashtable formVariables = new Hashtable(); - if (request.Method == HTTPMethod.Post) + + + + if (request.Method == HTTPMethod.Post && + request.ContentType.MainContentType == EnumMainContentType.MultiPart && + request.ContentType.SubContentType == EnumSubContentType.FormData) { + var contentLengthFromHeader = int.Parse(request.Headers["Content-Length"].ToString()); + var contentLengthReceived = 0; + var formCollection = new FormCollection(); + var socket = HttpContext.Socket; + var allDataRecieved = false; + var receivedByteCount = 0; + var buffer = new byte[2048]; + var ReadCount = 0; + var fileDirectoryPath = SDCardManager.GetWorkingDirectoryPath(); + var boundaryBytes = Encoding.UTF8.GetBytes(request.Boundary); + var strBldr = new StringBuilder(); + var BoundaryDataSeparator = "\r\n\r\n"; + var BoundaryDataIndex = 0; - // This form should be short so get it all data now, in one go. - // assume no content sent with first packet todo fix - int contentLengthFromHeader = int.Parse(request.Headers["Content-Length"].ToString()); - int contentLengthReceived = 0; - string requestContent = ""; - string fileName = ""; - string boundaryPattern = ""; - string fileDirectoryPath = SDCardManager.GetWorkingDirectoryPath(); + + Debug.Print("Boundary is: " + request.Boundary); //The Boundary property will be populated if it is multipart/form-data + socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount, true); //Discard the first Boundary Read... + SDCardManager.Write(fileDirectoryPath, "Test.txt", System.IO.FileMode.Create, buffer, receivedByteCount); + while (!allDataRecieved) { - if (contentLengthReceived < contentLengthFromHeader)// get next packet, this should have the start of any file in it. // todo put timeout - { - int count = 0; - byte[] data = HttpContext.Socket.GetMoreBytes(_PostRxBufferSize, out count); - requestContent += new string(Encoding.UTF8.GetChars(data, contentLengthReceived, count)); - contentLengthReceived += count; - } + contentLengthReceived += receivedByteCount; + Debug.Print("Bytes Read: " + receivedByteCount + + "\r\nTotal Bytes Read: " + contentLengthReceived + + "\r\nTotal Bytes To Read: " + contentLengthFromHeader); - var ContentType = request.Headers["Content-Type"]; //will have a value like "multipart/form-data; boundary=---------------------------2261521032598" - var boundarystring = ContentType.Split(new char[] { ';' })[1]; //gives me " boundary=---------------------------2261521032598" - string boundary = boundarystring.Split(new char[] { '=' })[1].ToString(); //gives me "---------------------------2261521032598" - //int nextBoundaryIndex = requestContent.IndexOf(boundary);// todo boundaries can change - boundaryPattern = "--" + boundary;//"#\n\n(.*)\n--$boundary#" - Regex MyRegex = new Regex(boundaryPattern, RegexOptions.Multiline); - string[] split = MyRegex.Split(requestContent); - for (int i = 0; i < split.Length; i++) + if (contentLengthReceived < contentLengthFromHeader) { - const string _ContentDispositionSearch = "Content-Disposition: form-data; name=\""; - int pos = split[i].IndexOf(_ContentDispositionSearch); - if (pos >= 0) - { - string remainder = split[i].Substring(pos + _ContentDispositionSearch.Length); - //ConsoleWrite.Print(remainder); - string[] nameSplit = remainder.Split(new char[] { '\"' }, 2); - string name = nameSplit[0]; - if (nameSplit[1][0] == ';') - {// file - int fileDataSeparatorIndex = nameSplit[1].IndexOf("\r\n\r\n"); // "\r\n\r\n" data starts after double new line - if (fileDataSeparatorIndex >= 0) - { - string fileNameSection = nameSplit[1].Substring(0, fileDataSeparatorIndex); - string[] fileNameSplit = fileNameSection.Split(new char[] { '\"' }); - formVariables.Add("fileName", fileNameSplit[1]); - fileName = fileNameSplit[1]; - string fileDataPart1 = nameSplit[1].Substring(fileDataSeparatorIndex + 4); - SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Create, fileDataPart1); - } - } - else - {// normal form variable - StringBuilder value = new StringBuilder(nameSplit[1]); - value = value.Replace("\r", "").Replace("\n", "").Replace("/", "\\"); - if (nameSplit[0] == "path") - { - fileDirectoryPath = SDCardManager.GetWorkingDirectoryPath() + value + "\\"; - } - formVariables.Add(nameSplit[0], value); - - - } - } + //while () + var data = socket.GetMoreBytes(2048, out receivedByteCount); + SDCardManager.Write(fileDirectoryPath, "Test.txt", System.IO.FileMode.Append, data, receivedByteCount); + //switch (ReadCount) + //{ + // case 1: + // socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount); //The Boundary property will be populated if it is multipart/form-data + // //Debug.Print("Discarding first read of Boundary..."); + // break; + // case 2: + // socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount); + // strBldr.Clear(); + // strBldr.Append(Encoding.UTF8.GetChars(buffer)); - } - } + // BoundaryDataIndex = strBldr.ToString().IndexOf(BoundaryDataSeparator); - // get the rest of the file and send to sd card - if (fileName.Length > 0)// todo what other checks - { - while (contentLengthReceived < contentLengthFromHeader)// get next packet, this should have the start of any file in it. // todo put timeout - { - byte[] data = null; - int count = 0; - { - data = HttpContext.Socket.GetMoreBytes(_PostRxBufferSize, out count); - contentLengthReceived += count; - //requestContent = new string(Encoding.UTF8.GetChars(data, 0, count)); - } - //ConsoleWrite.CollectMemoryAndPrint(true, System.Threading.Thread.CurrentThread.ManagedThreadId); - int boundaryPosition = requestContent.IndexOf(boundaryPattern); - if (boundaryPosition < 0) - {// no boundary so write all the bytes - SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, data, count); - } - else - {// boundary so write some of the bytes via a string - string fileContent = requestContent.Substring(0, boundaryPosition); - SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, fileContent); - } - // todo other params following + // fileDirectoryPath = fileDirectoryPath + strBldr.ToString().Substring(BoundaryDataIndex, strBldr.Length - BoundaryDataIndex).Trim('-').Trim(); + // Debug.Print("FileDirectoryPath: " + fileDirectoryPath); + // break; + // case 3: + // socket.ReceiveUntil(buffer, Encoding.UTF8.GetBytes(BoundaryDataSeparator), out receivedByteCount); + // strBldr.Clear(); + // strBldr.Append(Encoding.UTF8.GetChars(buffer)); + // Debug.Print("Data from 3rd Read" + strBldr); + // //SDCardManager.Write(fileDirectoryPath, "Text.txt", System.IO.FileMode.Create, buffer, receivedByteCount); + + // break; + // default: + // socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount); + // strBldr.Clear(); + // strBldr.Append(Encoding.UTF8.GetChars(buffer)); + // Debug.Print("Data from remaining Reads...\r\n" + strBldr); + // //SDCardManager.Write(fileDirectoryPath, "Text.txt", System.IO.FileMode.Append, buffer, receivedByteCount); + // break; + //} + //ReadCount += 1; + } + else + allDataRecieved = true; } + return formCollection; } - string message = string.Empty; - foreach (string key in formVariables.Keys) - message += "

" + key + ": " + formVariables[key].ToString() + "

"; - return message; + throw new NotSupportedException("Only POST is supported"); } } } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index da704d1..b6d4fbc 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -39,6 +39,7 @@ +
diff --git a/Rinsen.WebServer/Extensions/ExtensionMethods.cs b/Rinsen.WebServer/Extensions/ExtensionMethods.cs index 10e94cb..144d863 100644 --- a/Rinsen.WebServer/Extensions/ExtensionMethods.cs +++ b/Rinsen.WebServer/Extensions/ExtensionMethods.cs @@ -33,31 +33,12 @@ public static void ReceiveUntil(this Socket socket, byte[] buffer, string readUn public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUntil) { - // Clear buffer always to remove old junk if buffer is reused - Array.Clear(buffer, 0, buffer.Length); - var count = 0; - var matchCounter = 0; - while (socket.Available > 0 && count < buffer.Length) - { - socket.Receive(buffer, count, 1, SocketFlags.None); - if (buffer[count] == readUntil[matchCounter]) - { - matchCounter++; - if (matchCounter == readUntil.Length) - { - Array.Clear(buffer, count - readUntil.Length + 1, readUntil.Length); - break; - } - } - else - { - matchCounter = 0; - } - count++; - } + var bytesReceived = 0; + + ReceiveUntil(socket, buffer, readUntil, out bytesReceived); } - public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUntil, out int bytesRecieved) + public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUntil, out int bytesRecieved, bool leaveDelimeter = false) { // Clear buffer always to remove old junk if buffer is reused Array.Clear(buffer, 0, buffer.Length); @@ -73,7 +54,8 @@ public static void ReceiveUntil(this Socket socket, byte[] buffer, byte[] readUn matchCounter++; if (matchCounter == readUntil.Length) { - Array.Clear(buffer, count - readUntil.Length + 1, readUntil.Length); + if (!leaveDelimeter) + Array.Clear(buffer, count - readUntil.Length + 1, readUntil.Length); break; } } @@ -238,5 +220,29 @@ public static EnumSubContentType GetContentTypeSub(this string source) return EnumSubContentType.Undefined; } } + + public static int IndexOf(this byte[] source, byte[] patternToFind) + { + if (patternToFind.Length > source.Length) + return -1; + for (int i = 0; i < source.Length - patternToFind.Length; i++) + { + bool found = true; + for (int j = 0; j < patternToFind.Length; j++) + { + if (source[i + j] != patternToFind[j]) + { + found = false; + break; + } + } + if (found) + { + return i; + } + } + return -1; + } + } } From 587b186165351b31241c23a0a883d67b3137dc75 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Thu, 10 Dec 2015 17:17:46 -0600 Subject: [PATCH 11/14] Implemented Binary Upload Functionality Files are now saved to the SDCard as Byte Arrays which allows for Uploads of any file type. --- .../BoundaryHeaderCollection.cs | 49 ++++++ .../FileController.cs | 155 +++++++++++++----- ...en.WebServer.FileAndDirectoryServer.csproj | 1 + .../Extensions/ExtensionMethods.cs | 6 + 4 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 Rinsen.WebServer.FileAndDirectoryServer/BoundaryHeaderCollection.cs diff --git a/Rinsen.WebServer.FileAndDirectoryServer/BoundaryHeaderCollection.cs b/Rinsen.WebServer.FileAndDirectoryServer/BoundaryHeaderCollection.cs new file mode 100644 index 0000000..bc4a8c9 --- /dev/null +++ b/Rinsen.WebServer.FileAndDirectoryServer/BoundaryHeaderCollection.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.SPOT; + +using System.Collections; + + +namespace Rinsen.WebServer.FileAndDirectoryServer +{ + public class BoundaryHeaderCollection : Hashtable + { + public BoundaryHeaderCollection():base(){} + + public BoundaryHeaderCollection(string HeaderData) + : base() + { + var arrHeaders = HeaderData.Trim().Split('\n'); + foreach (var strLine in arrHeaders) + { + var arrHeaderKeyValue = strLine.Split(':'); + Add(arrHeaderKeyValue[0].Trim().ToLower(), arrHeaderKeyValue[1]); + } + } + + //public BoundaryHeaderCollection(string HeaderData) + // : base() + //{ + // var arrHeaders = HeaderData.Trim().Split('\n'); + // foreach (var strLine in arrHeaders) + // { + // Debug.Print("Header: " + strLine); + // var arrHeaderKeyValue = strLine.Split(':'); + // var arrSubKeys = arrHeaderKeyValue[1].Split(';'); + // var arrSubKeyKeyValues = new Hashtable(); + // foreach (var strValue in arrSubKeys) + // { + // Debug.Print("Subkey: " + strValue); + // if (strValue.IndexOf(';') != -1) + // { + // var arrTemp = strValue.Split(';'); + // arrSubKeyKeyValues.Add(arrTemp[0].Trim(), arrTemp[1]); + // } + // else + // arrSubKeyKeyValues.Add(strValue.Trim(), string.Empty); + // } + // Add(arrHeaderKeyValue[0].Trim(), arrSubKeyKeyValues); + // } + //} + } +} diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index 47b452f..7248808 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -13,7 +13,7 @@ namespace Rinsen.WebServer.FileAndDirectoryServer public class FileController : Controller { public ISDCardManager SDCardManager { get; set; } - private const int _PostRxBufferSize = 1500; + //private const int _PostRxBufferSize = 1500; public FormCollection RecieveFiles() { @@ -27,22 +27,27 @@ public FormCollection RecieveFiles() { var contentLengthFromHeader = int.Parse(request.Headers["Content-Length"].ToString()); var contentLengthReceived = 0; - var formCollection = new FormCollection(); + var PostedData = new FormCollection(); var socket = HttpContext.Socket; var allDataRecieved = false; var receivedByteCount = 0; - var buffer = new byte[2048]; - var ReadCount = 0; + var buffer = new byte[2048]; //The size of this array essentially sets the read rate... var fileDirectoryPath = SDCardManager.GetWorkingDirectoryPath(); var boundaryBytes = Encoding.UTF8.GetBytes(request.Boundary); - var strBldr = new StringBuilder(); - var BoundaryDataSeparator = "\r\n\r\n"; + var BeginningboundaryBytes = Encoding.UTF8.GetBytes("\r\n-----"); + var strBldrBoundaryHeader = new StringBuilder(); + var strBldrBoundaryData = new StringBuilder(); + StringBuilder strBldr = new StringBuilder(); + var BoundaryDataSeparator = Encoding.UTF8.GetBytes("\r\n\r\n"); //a double newline delimits Boundary Headers from Boundary Data... var BoundaryDataIndex = 0; + Regex rx = new Regex("([^=\\s]+)=\"([^\"]*)\""); //matches key value pairs like the below... + //var Value = "key1=\"value1\" key2=\"value 2\" key3=\"value3\" key4=\"value4\" key5=\"5555\" key6=\"xxx666\""; Debug.Print("Boundary is: " + request.Boundary); //The Boundary property will be populated if it is multipart/form-data + Debug.Print("Reading Bytes..."); socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount, true); //Discard the first Boundary Read... - SDCardManager.Write(fileDirectoryPath, "Test.txt", System.IO.FileMode.Create, buffer, receivedByteCount); + while (!allDataRecieved) { contentLengthReceived += receivedByteCount; @@ -52,50 +57,112 @@ public FormCollection RecieveFiles() if (contentLengthReceived < contentLengthFromHeader) { - //while () - var data = socket.GetMoreBytes(2048, out receivedByteCount); - SDCardManager.Write(fileDirectoryPath, "Test.txt", System.IO.FileMode.Append, data, receivedByteCount); - //switch (ReadCount) - //{ - // case 1: - // socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount); //The Boundary property will be populated if it is multipart/form-data - // //Debug.Print("Discarding first read of Boundary..."); - // break; - // case 2: - // socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount); - // strBldr.Clear(); - // strBldr.Append(Encoding.UTF8.GetChars(buffer)); - - // BoundaryDataIndex = strBldr.ToString().IndexOf(BoundaryDataSeparator); - - // fileDirectoryPath = fileDirectoryPath + strBldr.ToString().Substring(BoundaryDataIndex, strBldr.Length - BoundaryDataIndex).Trim('-').Trim(); - // Debug.Print("FileDirectoryPath: " + fileDirectoryPath); - // break; - // case 3: - // socket.ReceiveUntil(buffer, Encoding.UTF8.GetBytes(BoundaryDataSeparator), out receivedByteCount); - // strBldr.Clear(); - // strBldr.Append(Encoding.UTF8.GetChars(buffer)); - // Debug.Print("Data from 3rd Read" + strBldr); - // //SDCardManager.Write(fileDirectoryPath, "Text.txt", System.IO.FileMode.Create, buffer, receivedByteCount); - - // break; - // default: - // socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount); - // strBldr.Clear(); - // strBldr.Append(Encoding.UTF8.GetChars(buffer)); - // Debug.Print("Data from remaining Reads...\r\n" + strBldr); - // //SDCardManager.Write(fileDirectoryPath, "Text.txt", System.IO.FileMode.Append, buffer, receivedByteCount); - // break; - //} - //ReadCount += 1; + Debug.Print("Reading Bytes..."); + socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount, true); + BoundaryDataIndex = buffer.IndexOf(BoundaryDataSeparator); + //Debug.Print("Boundary Data Index: " + BoundaryDataIndex); + + /*The end of the multipart form is denoted by the boundary data followed by -- and a Carriage Return Line Feed (ex -----------------------------42291685921978--) + * Since I read until the the Boundary Data, I loop until only those characters are found; I do a check for null bytes just to make sure.*/ + if (buffer.IndexOf(Encoding.UTF8.GetBytes("--\r\n")) == 0 && buffer[5] == default(byte)) + { + Debug.Print("You've reached the end of the multipart form! "); + contentLengthReceived += receivedByteCount; + continue; + } + + strBldrBoundaryHeader.Clear(); + strBldrBoundaryHeader.Append(Encoding.UTF8.GetChars(buffer.SubArray(0, BoundaryDataIndex + 1))); + //Debug.Print("Boundary Header: \r\n" + strBldrBoundaryHeader.ToString().ToString().Trim()); + + var objBoundaryHeaderCollection = new BoundaryHeaderCollection(strBldrBoundaryHeader.ToString()); + + strBldr.Clear(); + strBldr.Append(objBoundaryHeaderCollection["Content-Disposition".ToLower()]); //I store all keys in lowercase... + //Debug.Print("Content-Disposition Value: " + strBldr); + var KeyValuePairs = new Hashtable(); + foreach (Match m in rx.Matches(strBldr.ToString())) + { + Debug.Print("Found KeyValue Pair:" + + "\r\n\tKey: " + m.Groups[1] + + "\r\n\tValue: " + m.Groups[2]); + KeyValuePairs.Add(m.Groups[1].ToString().ToLower(), m.Groups[2]); + + } + + if (objBoundaryHeaderCollection.Contains("Content-Type".ToLower()))//write Boundary Data to File... + { + strBldr.Clear();//Get the value portion of the Content-Type Boundary Header + strBldr.Append(objBoundaryHeaderCollection["Content-Type".ToLower()]); //I store all keys in lowercase... + Debug.Print("I've got a file and the Content-Type is: " + strBldr.ToString()); + + //Get the file's name from the KeyValuePairs... + string fileName = string.Empty; + if (KeyValuePairs.Contains("filename")) //JQuery ajax post (from ) sends filename parameter containing file's name; the name property is left as defined in the html. + fileName = KeyValuePairs["filename"].ToString(); + else if (KeyValuePairs.Contains("name")) //Form Post sends name parameter containing file's name (no filename parameter is sent) + fileName = KeyValuePairs["name"].ToString(); + else + fileName = "Unknown.txt"; + Debug.Print("File name is set to: " + fileName); + + var BoundaryData = buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, receivedByteCount - BoundaryDataIndex - BoundaryDataSeparator.Length); + if (BoundaryData.IndexOf(boundaryBytes) == -1) //Write to file, loop to get remaining file data + { + var intTemp = receivedByteCount; + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Create, BoundaryData, BoundaryData.Length); + while (buffer.IndexOf(boundaryBytes) == -1) //boundary hasn't been reached, get more data... + { + Debug.Print("Getting more File data..."); + socket.ReceiveUntil(buffer, boundaryBytes, out receivedByteCount, true); + if (buffer.IndexOf(boundaryBytes) != -1)//remove boundary string here before you have written to a file... + { + var intBeginningBoundaryBytesIndex = buffer.IndexOf(BeginningboundaryBytes); + var Data = buffer.SubArray(0, intBeginningBoundaryBytesIndex); + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, Data, Data.Length); + } + else + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, buffer, receivedByteCount); + intTemp += receivedByteCount; + Debug.Print("Iterated a loop and Recieved: " + receivedByteCount + " Bytes..."); + } + + receivedByteCount = intTemp; + } + else //remove boundary string, write to file and exit... + { + //remove boundary string here before you write to file... + var intBeginningBoundaryBytesIndex = buffer.IndexOf(BeginningboundaryBytes); + var Data = buffer.SubArray(0, intBeginningBoundaryBytesIndex); + SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, Data, Data.Length); + //SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Create, BoundaryData, BoundaryData.Length); + } + } + else //add the 'name' value and BoundaryData to the PostedData FormCollection object... + { + strBldrBoundaryData.Clear(); + strBldrBoundaryData.Append(Encoding.UTF8.GetChars(buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, buffer.Length - BoundaryDataIndex - BoundaryDataSeparator.Length))); + //Debug.Print("Boundary Data: \r\n" + strBldrBoundaryData.ToString()); + + var strBoundaryData = strBldrBoundaryData.ToString(); + if (KeyValuePairs.Contains("name")) + PostedData.AddValue(KeyValuePairs["name"].ToString(), strBoundaryData.Substring(0, strBoundaryData.IndexOf('\n'))); + Debug.Print("Added Key/Value Pair to PostedData FormCollection..."); + } + + + + + //var data = socket.GetMoreBytes(2048, out receivedByteCount); + //SDCardManager.Write(fileDirectoryPath, "Test.txt", System.IO.FileMode.Append, data, receivedByteCount); } else allDataRecieved = true; } - return formCollection; + return PostedData; } throw new NotSupportedException("Only POST is supported"); diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index b6d4fbc..f9b32ad 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -34,6 +34,7 @@ + diff --git a/Rinsen.WebServer/Extensions/ExtensionMethods.cs b/Rinsen.WebServer/Extensions/ExtensionMethods.cs index 144d863..ec8aa18 100644 --- a/Rinsen.WebServer/Extensions/ExtensionMethods.cs +++ b/Rinsen.WebServer/Extensions/ExtensionMethods.cs @@ -244,5 +244,11 @@ public static int IndexOf(this byte[] source, byte[] patternToFind) return -1; } + public static byte[] SubArray(this byte[] source, int index, int length) + { + byte[] result = new byte[length]; + Array.Copy(source, index, result, 0, length); + return result; + } } } From 8b0cff2e6d9489190d5842bac90b40c2b9d0373d Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Thu, 10 Dec 2015 21:57:06 -0600 Subject: [PATCH 12/14] Fixed issue with Files Read in 1 Pass I had an issue where the the boundary data was not removed in files that could be read within the first pass of the buffer. --- .../FileController.cs | 10 ++++------ Rinsen.WebServer/RequestContext.cs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index 7248808..b4b9b30 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -67,7 +67,6 @@ public FormCollection RecieveFiles() if (buffer.IndexOf(Encoding.UTF8.GetBytes("--\r\n")) == 0 && buffer[5] == default(byte)) { Debug.Print("You've reached the end of the multipart form! "); - contentLengthReceived += receivedByteCount; continue; } @@ -107,7 +106,8 @@ public FormCollection RecieveFiles() fileName = "Unknown.txt"; Debug.Print("File name is set to: " + fileName); - var BoundaryData = buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, receivedByteCount - BoundaryDataIndex - BoundaryDataSeparator.Length); + + var BoundaryData = buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, receivedByteCount - BoundaryDataIndex - BoundaryDataSeparator.Length + 1); if (BoundaryData.IndexOf(boundaryBytes) == -1) //Write to file, loop to get remaining file data { var intTemp = receivedByteCount; @@ -132,11 +132,9 @@ public FormCollection RecieveFiles() } else //remove boundary string, write to file and exit... { - //remove boundary string here before you write to file... - var intBeginningBoundaryBytesIndex = buffer.IndexOf(BeginningboundaryBytes); - var Data = buffer.SubArray(0, intBeginningBoundaryBytesIndex); + var intBeginningBoundaryBytesIndex = BoundaryData.IndexOf(BeginningboundaryBytes); + var Data = BoundaryData.SubArray(0, intBeginningBoundaryBytesIndex); SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, Data, Data.Length); - //SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Create, BoundaryData, BoundaryData.Length); } } else //add the 'name' value and BoundaryData to the PostedData FormCollection object... diff --git a/Rinsen.WebServer/RequestContext.cs b/Rinsen.WebServer/RequestContext.cs index 723d1d2..9ac2e36 100644 --- a/Rinsen.WebServer/RequestContext.cs +++ b/Rinsen.WebServer/RequestContext.cs @@ -62,7 +62,7 @@ public void SetHeader(string header) ContentType.SubContentType = subcontentData[0].GetContentTypeSub(); var boundaryData = subcontentData[1].Split('='); - Boundary = boundaryData[1].Trim('-'); + Boundary = boundaryData[1].TrimStart('-');//only remove the --'s because there is a \r\n on the other side of the number that is needed... } else { From 2e836e125f35052a7e7f76c2d922e04896dfe664 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Thu, 10 Dec 2015 22:36:35 -0600 Subject: [PATCH 13/14] Minor Bug Fix --- Rinsen.WebServer.FileAndDirectoryServer/FileController.cs | 6 ++++-- .../Rinsen.WebServer.FileAndDirectoryServer.csproj | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs index b4b9b30..b3c5053 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileController.cs @@ -107,9 +107,10 @@ public FormCollection RecieveFiles() Debug.Print("File name is set to: " + fileName); - var BoundaryData = buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, receivedByteCount - BoundaryDataIndex - BoundaryDataSeparator.Length + 1); - if (BoundaryData.IndexOf(boundaryBytes) == -1) //Write to file, loop to get remaining file data + + if (buffer.IndexOf(boundaryBytes) == -1) //Write to file, loop to get remaining file data { + var BoundaryData = buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, receivedByteCount - BoundaryDataIndex - BoundaryDataSeparator.Length); var intTemp = receivedByteCount; SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Create, BoundaryData, BoundaryData.Length); while (buffer.IndexOf(boundaryBytes) == -1) //boundary hasn't been reached, get more data... @@ -132,6 +133,7 @@ public FormCollection RecieveFiles() } else //remove boundary string, write to file and exit... { + var BoundaryData = buffer.SubArray(BoundaryDataIndex + BoundaryDataSeparator.Length, receivedByteCount - BoundaryDataIndex - BoundaryDataSeparator.Length + 1); var intBeginningBoundaryBytesIndex = BoundaryData.IndexOf(BeginningboundaryBytes); var Data = BoundaryData.SubArray(0, intBeginningBoundaryBytesIndex); SDCardManager.Write(fileDirectoryPath, fileName, System.IO.FileMode.Append, Data, Data.Length); diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index f9b32ad..8851e16 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -40,7 +40,6 @@ - From 1df3f1fdf3a4d19ccce9688e15d9e3ddadc2a080 Mon Sep 17 00:00:00 2001 From: JakeLardinois Date: Fri, 15 Jan 2016 16:16:53 -0600 Subject: [PATCH 14/14] Misc Adjustments --- NetduinoSDCard/SDCard.cs | 9 ++++++++- .../FileAndDirectoryService.cs | 2 +- .../Rinsen.WebServer.FileAndDirectoryServer.csproj | 3 +-- Rinsen.WebServer/ServerBase.cs | 5 +++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/NetduinoSDCard/SDCard.cs b/NetduinoSDCard/SDCard.cs index 6827446..789d93c 100644 --- a/NetduinoSDCard/SDCard.cs +++ b/NetduinoSDCard/SDCard.cs @@ -28,7 +28,14 @@ public SDCard(string WorkingDirectory) public DirectoryInfo WorkingDirectoryInfo { get; set; } public void SetWorkingDirectoryInfo(string WorkingDirectory) { - var strPath = MountDirectoryPath + WorkingDirectory; + string strPath = string.Empty; + + + if (WorkingDirectory.LastIndexOf("\\") == WorkingDirectory.Length - 1) + strPath = MountDirectoryPath + WorkingDirectory; + else + strPath = MountDirectoryPath + WorkingDirectory + "\\"; + CreateDirectory(strPath); WorkingDirectoryInfo = new DirectoryInfo(strPath); } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs index 31fe430..0ef727b 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs +++ b/Rinsen.WebServer.FileAndDirectoryServer/FileAndDirectoryService.cs @@ -95,7 +95,7 @@ public string GetFileServiceBasePath() Debug.Print("Setting File Services Base Path..." + "\r\nHas an SDCard Manager: " + HasSDCardManager); if (HasSDCardManager) { - basePath = SDCardManager.GetWorkingDirectoryPath(); + basePath = SDCardManager.GetWorkingDirectoryPath().TrimEnd(new char[] { '\\' }); Debug.Print("Base path is: " + basePath); return basePath; } diff --git a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj index 8851e16..cd45444 100644 --- a/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj +++ b/Rinsen.WebServer.FileAndDirectoryServer/Rinsen.WebServer.FileAndDirectoryServer.csproj @@ -42,8 +42,7 @@ - - + diff --git a/Rinsen.WebServer/ServerBase.cs b/Rinsen.WebServer/ServerBase.cs index add3f00..1dd77b5 100644 --- a/Rinsen.WebServer/ServerBase.cs +++ b/Rinsen.WebServer/ServerBase.cs @@ -68,6 +68,11 @@ public void SetFileAndDirectoryService(IFileAndDirectoryService fileAndDirectory _serverContext.FileServerBasePath = fileAndDirectoryService.GetFileServiceBasePath(); } + public void SetHostName(string hostName) + { + _serverContext.HostName = hostName; + } + public bool ThreadedResponses { get