Allow multiple value processing for Form and QueryString values. (#4736)

* Allow multiple value processing for Form and QueryString values.

* refactor HTTPEndpoint

* add multi-part form handling

* cleanup

* reoder form handling code
This commit is contained in:
Mohamed Ali 2024-01-21 12:52:46 +03:00 committed by GitHub
parent 5edda57c5b
commit 00331f7497
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 90 additions and 104 deletions

View file

@ -211,8 +211,8 @@ public class HttpEndpoint : Trigger<HttpRequest>
var routeData = GetRouteData(httpContext, path);
var routeDictionary = routeData.Values.ToDictionary(route => route.Key, route => route.Value!);
var queryStringDictionary = httpContext.Request.Query.ToDictionary<KeyValuePair<string, StringValues>, string, object>(queryString => queryString.Key, queryString => queryString.Value[0]!);
var headersDictionary = httpContext.Request.Headers.ToDictionary<KeyValuePair<string, StringValues>, string, object>(header => header.Key, header => header.Value[0]!);
var queryStringDictionary = httpContext.Request.Query.ToObjectDictionary();
var headersDictionary = httpContext.Request.Headers.ToObjectDictionary();
context.Set(RouteData, routeDictionary);
context.Set(QueryStringData, queryStringDictionary);
@ -225,48 +225,60 @@ public class HttpEndpoint : Trigger<HttpRequest>
return;
}
// Read content, if any.
try
// Handle Form Fields
if (request.HasFormContentType)
{
var content = await ParseContentAsync(context, request);
ParsedContent.Set(context, content);
var formFields = request.Form.ToObjectDictionary();
ParsedContent.Set(context, formFields);
// Read files, if any.
var files = ReadFilesAsync(context, request);
if (files.Any())
{
if (!ValidateFileSizes(context, httpContext, files))
{
await HandleFileSizeTooLargeAsync(context, httpContext);
return;
}
if (!ValidateFileExtensionWhitelist(context, httpContext, files))
{
await HandleInvalidFileExtensionWhitelistAsync(context, httpContext);
return;
}
if (!ValidateFileExtensionBlacklist(context, httpContext, files))
{
await HandleInvalidFileExtensionBlacklistAsync(context, httpContext);
return;
}
if (!ValidateFileMimeTypes(context, httpContext, files))
{
await HandleInvalidFileMimeTypesAsync(context, httpContext);
return;
}
Files.Set(context, files.ToArray());
}
}
catch (JsonException e)
else
{
await HandleInvalidJsonPayloadAsync(context, httpContext, e);
throw;
}
// Read files, if any.
var files = ReadFilesAsync(context, request);
if (files.Any())
{
if (!ValidateFileSizes(context, httpContext, files))
// Parse Non-Form content.
try
{
await HandleFileSizeTooLargeAsync(context, httpContext);
return;
var content = await ParseContentAsync(context, request);
ParsedContent.Set(context, content);
}
catch (JsonException e)
{
await HandleInvalidJsonPayloadAsync(context, httpContext, e);
throw;
}
if (!ValidateFileExtensionWhitelist(context, httpContext, files))
{
await HandleInvalidFileExtensionWhitelistAsync(context, httpContext);
return;
}
if (!ValidateFileExtensionBlacklist(context, httpContext, files))
{
await HandleInvalidFileExtensionBlacklistAsync(context, httpContext);
return;
}
if (!ValidateFileMimeTypes(context, httpContext, files))
{
await HandleInvalidFileMimeTypesAsync(context, httpContext);
return;
}
Files.Set(context, files.ToArray());
}
// Complete.

View file

@ -0,0 +1,28 @@
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Elsa.Extensions;
/// <summary>
/// Contains extension methods for the <see cref="StringValues"/> Enumerable dictonaries.
/// </summary>
public static class StringValueExtensions
{
/// <summary>
/// Convert the collection to the desired dictionary type.
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
public static Dictionary<string, object> ToObjectDictionary(this IEnumerable<KeyValuePair<string, StringValues>> collection)
{
return collection.ToDictionary<KeyValuePair<string, StringValues>, string, object>(
item => item.Key,
item => item.Value.Count <= 1 ?
item.Value[0]!
: item.Value.ToArray());
}
}

View file

@ -158,12 +158,10 @@ public class HttpFeature : FeatureBase
.AddRequestHandler<ValidateWorkflowRequestHandler, ValidateWorkflowRequest, ValidateWorkflowResponse>()
.AddNotificationHandler<UpdateRouteTable>()
// Content parsers.
.AddScoped<IHttpContentParser, StringHttpContentParser>()
.AddScoped<IHttpContentParser, JsonHttpContentParser>()
.AddScoped<IHttpContentParser, XmlHttpContentParser>()
.AddScoped<IHttpContentParser, FormHttpContentParser>()
// Content parsers.
.AddSingleton<IHttpContentParser, JsonHttpContentParser>()
.AddSingleton<IHttpContentParser, XmlHttpContentParser>()
// HTTP content factories.
.AddScoped<IHttpContentFactory, TextContentFactory>()
.AddScoped<IHttpContentFactory, JsonContentFactory>()

View file

@ -1,28 +0,0 @@
using System.Xml.Serialization;
using Elsa.Extensions;
using Elsa.Http.Contracts;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
namespace Elsa.Http.Parsers;
/// <summary>
/// Reads application/xml and text/xml content type streams.
/// </summary>
public class FormHttpContentParser : IHttpContentParser
{
/// <inheritdoc />
public int Priority => 0;
/// <inheritdoc />
public bool GetSupportsContentType(string contentType) => contentType.StartsWith("application/x-www-form-urlencoded", StringComparison.InvariantCultureIgnoreCase);
/// <inheritdoc />
public async Task<object> ReadAsync(Stream content, Type? returnType, CancellationToken cancellationToken)
{
using var reader = new FormReader(content);
return (await reader.ReadFormAsync(cancellationToken)).ToDictionary<KeyValuePair<string, StringValues>, string, object>(formField => formField.Key, formField => formField.Value[0]!); ;
}
}

View file

@ -1,24 +0,0 @@
using Elsa.Expressions.Helpers;
using Elsa.Http.Contracts;
namespace Elsa.Http.Parsers;
/// <summary>
/// Reads any content type streams as a string and attempts to convert the string to the specified return type.
/// </summary>
public class StringHttpContentParser : IHttpContentParser
{
/// <inheritdoc />
public int Priority => -10;
/// <inheritdoc />
public bool GetSupportsContentType(string contentType) => !contentType.StartsWith("multipart/form-data");
/// <inheritdoc />
public async Task<object> ReadAsync(Stream content, Type? returnType, CancellationToken cancellationToken)
{
using var reader = new StreamReader(content, leaveOpen: true);
var text = await reader.ReadToEndAsync();
return returnType == null ? text : text.ConvertTo(returnType)!;
}
}

View file

@ -36,8 +36,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
{
var options = GetSerializerOptions();
var serializingWorkflowState = new SerializingWorkflowState(options);
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
return JsonSerializer.Serialize(workflowState, options);
}
@ -46,8 +46,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
{
var options = GetSerializerOptions();
var serializingWorkflowState = new SerializingWorkflowState(options);
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
return JsonSerializer.SerializeToUtf8Bytes(workflowState, options);
}
@ -56,8 +56,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
{
var options = GetSerializerOptions();
var serializingWorkflowState = new SerializingWorkflowState(options);
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
return JsonSerializer.SerializeToElement(workflowState, options);
}
@ -110,8 +110,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
options.Converters.Add(JsonMetadataServices.TimeSpanConverter);
options.Converters.Add(new PolymorphicObjectConverterFactory());
options.Converters.Add(new TypeJsonConverter(_wellKnownTypeRegistry));
options.Converters.Add(new VariableConverterFactory(_wellKnownTypeRegistry, _loggerFactory));
options.Converters.Add(new VariableConverterFactory(_wellKnownTypeRegistry, _loggerFactory));
return options;
}
}