Skip to content

Commit

Permalink
Add features for parsing form data (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottoffen authored Feb 25, 2022
1 parent 2a46122 commit ca31d7d
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Grapeseed/IHttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public interface IHttpRequest

Stream InputStream { get; }

/// <summary>
/// Gets the multipart boundary, returns empty string if not available
/// </summary>
string MultipartBoundary { get; }

/// <summary>
/// Gets a representation of the HttpMethod and Endpoint of the request
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Grapevine/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public class HttpRequest : IHttpRequest

public Stream InputStream => Advanced.InputStream;

public string MultipartBoundary { get; }

public string Name => $"{HttpMethod} {Endpoint}";

public string Endpoint { get; protected set; }
Expand Down Expand Up @@ -58,6 +60,7 @@ public HttpRequest(HttpListenerRequest request)
Advanced = request;
Endpoint = request.Url.AbsolutePath.TrimEnd('/');
HostPrefix = request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
MultipartBoundary = this.GetMultipartBoundary();
}
}
}
47 changes: 47 additions & 0 deletions src/Grapevine/HttpRequestExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace Grapevine
{
public static class HttpRequestExtensions
{
private static readonly string _multipartContentType = "multipart/form-data; boundary=";
private static readonly int _startIndex = _multipartContentType.Length;

internal static string GetMultipartBoundary(this IHttpRequest request)
{
return (string.IsNullOrWhiteSpace(request.ContentType) || !request.ContentType.StartsWith(_multipartContentType))
? string.Empty
: request.ContentType.Substring(_startIndex);
}

public static async Task<IDictionary<string, string>> ParseFormUrlEncodedData(this IHttpRequest request)
{
var data = new Dictionary<string, string>();

using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
{
var payload = await reader.ReadToEndAsync();

foreach (var kvp in payload.Split('&'))
{
var pair = kvp.Split('=');
var key = pair[0];
var value = pair[1];

var decoded = string.Empty;
while((decoded = Uri.UnescapeDataString(value)) != value)
{
value = decoded;
}

data.Add(key, value);
}
}

return data;
}
}
}
13 changes: 13 additions & 0 deletions src/Grapevine/Middleware/FormUrlEncodedData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading.Tasks;

namespace Grapevine.Middleware
{
public static class FormUrlEncodedData
{
public async static Task Parse(IHttpContext context, IRestServer server)
{
if (context.Request.ContentType != "application/x-www-form-urlencoded") return;
context.Locals.TryAdd("FormData", await context.Request.ParseFormUrlEncodedData());
}
}
}
7 changes: 7 additions & 0 deletions src/Grapevine/MiddlewareExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public static void Run(this IRestServer server, CancellationToken token)
server.AfterStopping -= onStop;
}

public static IRestServer AutoParseFormUrlEncodedData(this IRestServer server)
{
server.OnRequestAsync -= FormUrlEncodedData.Parse;
server.OnRequestAsync += FormUrlEncodedData.Parse;
return server;
}

public static IRestServer UseContentFolders(this IRestServer server)
{
server.OnRequestAsync -= ContentFolders.SendFileIfExistsAsync;
Expand Down
36 changes: 36 additions & 0 deletions src/Samples/Resources/FormDataResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using Grapevine;
using HttpMultipartParser;
using Microsoft.Extensions.Logging;

namespace Samples.Resources
{
[RestResource(BasePath = "form")]
public class FormDataResource
{
private readonly ILogger<FormDataResource> _logger;

public FormDataResource(ILogger<FormDataResource> logger)
{
_logger = logger;
}

[RestRoute("Post", "/submit/data", Name = "Upload form data", Description = "This demonstrates how to parse application/www-form-urlencoded data.")]
[Header("Content-Type", "application/x-www-form-urlencoded")]
public async Task ParseUrlEncodedFormData(IHttpContext context)
{
// set a breakpoint here to see the auto-parsed data
await context.Response.SendResponseAsync(HttpStatusCode.Ok);
}

[RestRoute("Post", "/submit/data", Name = "Upload form data", Description = "This demonstrates how to parse simple multipart/form-data.")]
[Header("Content-Type", "multipart/form-data")]
public async Task ParseMultipartFormData(IHttpContext context)
{
// /~https://github.com/Http-Multipart-Data-Parser/Http-Multipart-Data-Parser
var content = await MultipartFormDataParser.ParseAsync(context.Request.InputStream, context.Request.MultipartBoundary, context.Request.ContentEncoding);
var name = $"{content.GetParameterValue("FirstName")} {content.GetParameterValue("LastName")} : {content.Files.Count}";
await context.Response.SendResponseAsync(name);
}
}
}
1 change: 1 addition & 0 deletions src/Samples/Samples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="HttpMultipartParser" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
<PackageReference Include="NLog" Version="4.7.6" />
Expand Down
3 changes: 3 additions & 0 deletions src/Samples/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public void ConfigureServer(IRestServer server)

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

/* Configure server to auto parse application/x-www-for-urlencoded data*/
server.AutoParseFormUrlEncodedData();

/* Configure Router Options (if supported by your router implementation) */
server.Router.Options.SendExceptionMessages = true;
}
Expand Down

0 comments on commit ca31d7d

Please sign in to comment.