Skip to content

Commit ca31d7d

Browse files
authored
Add features for parsing form data (#105)
1 parent 2a46122 commit ca31d7d

File tree

8 files changed

+115
-0
lines changed

8 files changed

+115
-0
lines changed

src/Grapeseed/IHttpRequest.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ public interface IHttpRequest
5252

5353
Stream InputStream { get; }
5454

55+
/// <summary>
56+
/// Gets the multipart boundary, returns empty string if not available
57+
/// </summary>
58+
string MultipartBoundary { get; }
59+
5560
/// <summary>
5661
/// Gets a representation of the HttpMethod and Endpoint of the request
5762
/// </summary>

src/Grapevine/HttpRequest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public class HttpRequest : IHttpRequest
2929

3030
public Stream InputStream => Advanced.InputStream;
3131

32+
public string MultipartBoundary { get; }
33+
3234
public string Name => $"{HttpMethod} {Endpoint}";
3335

3436
public string Endpoint { get; protected set; }
@@ -58,6 +60,7 @@ public HttpRequest(HttpListenerRequest request)
5860
Advanced = request;
5961
Endpoint = request.Url.AbsolutePath.TrimEnd('/');
6062
HostPrefix = request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
63+
MultipartBoundary = this.GetMultipartBoundary();
6164
}
6265
}
6366
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading.Tasks;
5+
6+
namespace Grapevine
7+
{
8+
public static class HttpRequestExtensions
9+
{
10+
private static readonly string _multipartContentType = "multipart/form-data; boundary=";
11+
private static readonly int _startIndex = _multipartContentType.Length;
12+
13+
internal static string GetMultipartBoundary(this IHttpRequest request)
14+
{
15+
return (string.IsNullOrWhiteSpace(request.ContentType) || !request.ContentType.StartsWith(_multipartContentType))
16+
? string.Empty
17+
: request.ContentType.Substring(_startIndex);
18+
}
19+
20+
public static async Task<IDictionary<string, string>> ParseFormUrlEncodedData(this IHttpRequest request)
21+
{
22+
var data = new Dictionary<string, string>();
23+
24+
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
25+
{
26+
var payload = await reader.ReadToEndAsync();
27+
28+
foreach (var kvp in payload.Split('&'))
29+
{
30+
var pair = kvp.Split('=');
31+
var key = pair[0];
32+
var value = pair[1];
33+
34+
var decoded = string.Empty;
35+
while((decoded = Uri.UnescapeDataString(value)) != value)
36+
{
37+
value = decoded;
38+
}
39+
40+
data.Add(key, value);
41+
}
42+
}
43+
44+
return data;
45+
}
46+
}
47+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Threading.Tasks;
2+
3+
namespace Grapevine.Middleware
4+
{
5+
public static class FormUrlEncodedData
6+
{
7+
public async static Task Parse(IHttpContext context, IRestServer server)
8+
{
9+
if (context.Request.ContentType != "application/x-www-form-urlencoded") return;
10+
context.Locals.TryAdd("FormData", await context.Request.ParseFormUrlEncodedData());
11+
}
12+
}
13+
}

src/Grapevine/MiddlewareExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ public static void Run(this IRestServer server, CancellationToken token)
4444
server.AfterStopping -= onStop;
4545
}
4646

47+
public static IRestServer AutoParseFormUrlEncodedData(this IRestServer server)
48+
{
49+
server.OnRequestAsync -= FormUrlEncodedData.Parse;
50+
server.OnRequestAsync += FormUrlEncodedData.Parse;
51+
return server;
52+
}
53+
4754
public static IRestServer UseContentFolders(this IRestServer server)
4855
{
4956
server.OnRequestAsync -= ContentFolders.SendFileIfExistsAsync;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Threading.Tasks;
2+
using Grapevine;
3+
using HttpMultipartParser;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace Samples.Resources
7+
{
8+
[RestResource(BasePath = "form")]
9+
public class FormDataResource
10+
{
11+
private readonly ILogger<FormDataResource> _logger;
12+
13+
public FormDataResource(ILogger<FormDataResource> logger)
14+
{
15+
_logger = logger;
16+
}
17+
18+
[RestRoute("Post", "/submit/data", Name = "Upload form data", Description = "This demonstrates how to parse application/www-form-urlencoded data.")]
19+
[Header("Content-Type", "application/x-www-form-urlencoded")]
20+
public async Task ParseUrlEncodedFormData(IHttpContext context)
21+
{
22+
// set a breakpoint here to see the auto-parsed data
23+
await context.Response.SendResponseAsync(HttpStatusCode.Ok);
24+
}
25+
26+
[RestRoute("Post", "/submit/data", Name = "Upload form data", Description = "This demonstrates how to parse simple multipart/form-data.")]
27+
[Header("Content-Type", "multipart/form-data")]
28+
public async Task ParseMultipartFormData(IHttpContext context)
29+
{
30+
// https://github.com/Http-Multipart-Data-Parser/Http-Multipart-Data-Parser
31+
var content = await MultipartFormDataParser.ParseAsync(context.Request.InputStream, context.Request.MultipartBoundary, context.Request.ContentEncoding);
32+
var name = $"{content.GetParameterValue("FirstName")} {content.GetParameterValue("LastName")} : {content.Files.Count}";
33+
await context.Response.SendResponseAsync(name);
34+
}
35+
}
36+
}

src/Samples/Samples.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</ItemGroup>
66

77
<ItemGroup>
8+
<PackageReference Include="HttpMultipartParser" Version="5.0.1" />
89
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
910
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
1011
<PackageReference Include="NLog" Version="4.7.6" />

src/Samples/Startup.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ public void ConfigureServer(IRestServer server)
4848

4949
server.Prefixes.Add($"http://localhost:{_serverPort}/");
5050

51+
/* Configure server to auto parse application/x-www-for-urlencoded data*/
52+
server.AutoParseFormUrlEncodedData();
53+
5154
/* Configure Router Options (if supported by your router implementation) */
5255
server.Router.Options.SendExceptionMessages = true;
5356
}

0 commit comments

Comments
 (0)