Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,12 +373,24 @@ if (url.ToLower().IndexOf("/api/") == 0)
{

ret += $"Size of content: {e.Context.Request.ContentLength64}\r\n";
byte[] buff = new byte[e.Context.Request.ContentLength64];
e.Context.Request.InputStream.Read(buff, 0, buff.Length);
ret += $"Hex string representation:\r\n";
for (int i = 0; i < buff.Length; i++)

var contentTypes = e.Context.Request.Headers?.GetValues("Content-Type");
var isMultipartForm = contentTypes != null && contentTypes.Length > 0 && contentTypes[0].StartsWith("multipart/form-data;");

if(isMultipartForm)
{
ret += buff[i].ToString("X") + " ";
var form = e.Context.Request.ReadForm();
ret += $"Received a form with {form.Parameters.Length} parameters and {form.Files.Length} files.";
}
else
{
var body = e.Context.Request.ReadBody();

ret += $"Request body hex string representation:\r\n";
for (int i = 0; i < body.Length; i++)
{
ret += body[i].ToString("X") + " ";
}
}

}
Expand All @@ -391,6 +403,11 @@ This API example is basic but as you get the method, you can choose what to do.

As you get the url, you can check for a specific controller called. And you have the parameters and the content payload!

Notice the extension methods to read the body of the request:

- ReadBody will read the data from the InputStream while the data is flowing in which might be in multiple passes depending on the size of the body
- ReadForm allows to read a multipart/form-data form and returns the text key/value pairs as well as any files in the request

Example of a result with call:

![result](./doc/POSTcapture.jpg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,49 +32,30 @@
</PropertyGroup>
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
<ItemGroup>
<Compile Include="..\nanoFramework.WebServer\Authentication.cs" Link="Authentication.cs" />
<Compile Include="..\nanoFramework.WebServer\AuthenticationAttribute.cs" Link="AuthenticationAttribute.cs" />
<Compile Include="..\nanoFramework.WebServer\AuthenticationType.cs" Link="AuthenticationType.cs" />
<Compile Include="..\nanoFramework.WebServer\CallbackRoutes.cs" Link="CallbackRoutes.cs" />
<Compile Include="..\nanoFramework.WebServer\CaseSensitiveAttribute.cs" Link="CaseSensitiveAttribute.cs" />
<Compile Include="..\nanoFramework.WebServer\Header.cs" Link="Header.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpListenerRequestExtensions.cs" Link="HttpListenerRequestExtensions.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\FilePart.cs" Link="HttpMultipartParser\FilePart.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\HashtableUtility.cs" Link="HttpMultipartParser\HashtableUtility.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\HeaderUtility.cs" Link="HttpMultipartParser\HeaderUtility.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\LineBuffer.cs" Link="HttpMultipartParser\LineBuffer.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\LineReader.cs" Link="HttpMultipartParser\LineReader.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\MultipartFormDataParser.cs" Link="HttpMultipartParser\MultipartFormDataParser.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\MultipartFormDataParserException.cs" Link="HttpMultipartParser\MultipartFormDataParserException.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpMultipartParser\ParameterPart.cs" Link="HttpMultipartParser\ParameterPart.cs" />
<Compile Include="..\nanoFramework.WebServer\HttpProtocol.cs" Link="HttpProtocol.cs" />
<Compile Include="..\nanoFramework.WebServer\MethodAttribute.cs" Link="MethodAttribute.cs" />
<Compile Include="..\nanoFramework.WebServer\RouteAttribute.cs" Link="RouteAttribute.cs" />
<Compile Include="..\nanoFramework.WebServer\UrlParameter.cs" Link="UrlParameter.cs" />
<Compile Include="..\nanoFramework.WebServer\WebServer.cs" Link="WebServer.cs" />
<Compile Include="..\nanoFramework.WebServer\WebServerEventArgs.cs" Link="WebServerEventArgs.cs" />
<Compile Include="..\nanoFramework.WebServer\WebServerStatus.cs" Link="WebServerStatus.cs" />
<Compile Include="..\nanoFramework.WebServer\WebServerStatusEventArgs.cs" Link="WebServerStatusEventArgs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\nanoFramework.WebServer\Authentication.cs">
<Link>Authentication.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\AuthenticationAttirbute.cs">
<Link>AuthenticationAttirbute.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\AuthenticationType.cs">
<Link>AuthenticationType.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\CallbackRoutes.cs">
<Link>CallbackRoutes.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\CaseSensitiveAttribute.cs">
<Link>CaseSensitiveAttribute.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\HttpProtocol.cs">
<Link>HttpProtocol.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\WebServerEventArgs.cs">
<Link>WebServerEventArgs.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\Header.cs">
<Link>Header.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\MethodAttribute.cs">
<Link>MethodAttribute.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\RouteAttribute.cs">
<Link>RouteAttribute.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\UrlParameter.cs">
<Link>UrlParameter.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\WebServer.cs">
<Link>WebServer.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\WebServerStatus.cs">
<Link>WebServerStatus.cs</Link>
</Compile>
<Compile Include="..\nanoFramework.WebServer\WebServerStatusEventArgs.cs">
<Link>WebServerStatusEventArgs.cs</Link>
</Compile>
<None Include="..\key.snk" />
</ItemGroup>
<ItemGroup>
Expand Down Expand Up @@ -118,6 +99,9 @@
<ItemGroup>
<Content Include="packages.lock.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="HttpMultipartParser\" />
</ItemGroup>
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets')" />
<ProjectExtensions>
<ProjectCapabilities>
Expand Down
10 changes: 0 additions & 10 deletions nanoFramework.WebServer/HttpConnectionType.cs

This file was deleted.

65 changes: 65 additions & 0 deletions nanoFramework.WebServer/HttpListenerRequestExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Net;
using System.Threading;
using nanoFramework.WebServer.HttpMultipartParser;

namespace nanoFramework.WebServer
{
/// <summary>Contains extension methods for HttpListenerRequest</summary>
public static class HttpListenerRequestExtensions
{
/// <summary>
/// Reads a Multipart form from the request
/// </summary>
/// <param name="httpListenerRequest">The request to read the form from</param>
/// <returns>A <see cref="MultipartFormDataParser">MultipartFormDataParser</see> containing a collection of the parameters and files in the form.</returns>
public static MultipartFormDataParser ReadForm(this HttpListenerRequest httpListenerRequest) =>
MultipartFormDataParser.Parse(httpListenerRequest.InputStream);

/// <summary>
/// Reads a body from the HttpListenerRequest inputstream
/// </summary>
/// <param name="httpListenerRequest">The request to read the body from</param>
/// <returns>A byte[] containing the body of the request</returns>
public static byte[] ReadBody(this HttpListenerRequest httpListenerRequest)
{
byte[] body = new byte[httpListenerRequest.ContentLength64];
byte[] buffer = new byte[4096];
Stream stream = httpListenerRequest.InputStream;

int position = 0;

while (true)
{
// The stream is (should be) a NetworkStream which might still be receiving data while
// we're already processing. Give the stream a chance to receive more data or we might
// end up with "zero bytes read" too soon...
Thread.Sleep(1);

long length = stream.Length;

if (length > buffer.Length)
{
length = buffer.Length;
}

int bytesRead = stream.Read(buffer, 0, (int)length);

if (bytesRead == 0)
{
break;
}

Array.Copy(buffer, 0, body, position, bytesRead);

position += bytesRead;
}

return body;
}
}
}
80 changes: 80 additions & 0 deletions nanoFramework.WebServer/HttpMultipartParser/FilePart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.IO;

namespace nanoFramework.WebServer.HttpMultipartParser
{
/// <summary>Represents a single file extracted from a multipart/form-data stream.</summary>
public class FilePart
{
/// <summary>Initializes a new instance of the <see cref="FilePart" /> class.</summary>
/// <param name="name">The name of the input field used for the upload.</param>
/// <param name="fileName">The name of the file.</param>
/// <param name="data">The file data.</param>
/// <param name="additionalProperties">Additional properties associated with this file.</param>
/// <param name="contentType">The content type.</param>
/// <param name="contentDisposition">The content disposition.</param>
public FilePart(string name, string fileName, Stream data, Hashtable additionalProperties, string contentType, string contentDisposition)
{
string[] parts = fileName?.Split(GetInvalidFileNameChars());

Name = name;
FileName = parts != null && parts.Length > 0 ? parts[parts.Length - 1] : string.Empty;
Data = data;
ContentType = contentType;
ContentDisposition = contentDisposition;
AdditionalProperties = additionalProperties;
}

/// <summary>Gets the data.</summary>
public Stream Data
{
get;
}

/// <summary>Gets the file name.</summary>
public string FileName
{
get;
}

/// <summary>Gets the name.</summary>
public string Name
{
get;
}

/// <summary>Gets the content-type. Defaults to text/plain if unspecified.</summary>
public string ContentType
{
get;
}

/// <summary>Gets the content-disposition. Defaults to form-data if unspecified.</summary>
public string ContentDisposition
{
get;
}

/// <summary>
/// Gets the additional properties associated with this file.
/// An additional property is any property other than the "well known" ones such as name, filename, content-type, etc.
/// </summary>
public Hashtable AdditionalProperties
{
get;
private set;
}

private static char[] GetInvalidFileNameChars() => new char[]
{
'\"', '<', '>', '|', '\0',
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
(char)31, ':', '*', '?', '\\', '/'
};
}
}
23 changes: 23 additions & 0 deletions nanoFramework.WebServer/HttpMultipartParser/HashtableUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;

namespace nanoFramework.WebServer.HttpMultipartParser
{
internal static class HashtableUtility
{
public static bool TryGetValue(this Hashtable hashtable, string key, out string value)
{
if (hashtable != null && hashtable.Contains(key))
{
var obj = hashtable[key];
value = obj == null ? string.Empty : obj.ToString();
return true;
}

value = null;
return false;
}
}
}
72 changes: 72 additions & 0 deletions nanoFramework.WebServer/HttpMultipartParser/HeaderUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Text;

namespace nanoFramework.WebServer.HttpMultipartParser
{
/// <summary>
/// Provides parsing headers from a Http Multipart Form
/// </summary>
public static class HeaderUtility
{
/// <summary>
/// Reads headers from a line of text.
/// Headers are delimited by a semi-colon ';'
/// Key-value pairs are separated by colon ':' or equals '='
/// Values can be delimited by quotes '"' or not
/// </summary>
/// <param name="text">The line of text containing one or more headers</param>
/// <param name="headers">
/// The hashtable that will receive the key values.
/// Passed in since a Multipart Part can contain multiple lines of headers
/// </param>
public static void ParseHeaders(string text, Hashtable headers)
{
bool inQuotes = false;
bool inKey = true;
StringBuilder key = new();
StringBuilder value = new();

foreach (char c in text)
{
if (c == '"')
{
inQuotes = !inQuotes;
}
else if (inQuotes)
{
value.Append(c);
}
else if (c == ';')
{
headers[key.ToString().ToLower()] = value.ToString();
key.Clear();
inKey = true;
}
else if (c == '=' || c == ':')
{
value = value.Clear();
inKey = false;
}
else if (c != ' ')
{
if (inKey)
{
key.Append(c);
}
else
{
value.Append(c);
}
}
}

if (key.Length > 0)
{
headers.Add(key.ToString().ToLower(), value.ToString());
}
}
}
}
Loading