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:
parent
5edda57c5b
commit
00331f7497
|
|
@ -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.
|
||||
|
|
|
|||
28
src/modules/Elsa.Http/Extensions/StringValueExtensions.cs
Normal file
28
src/modules/Elsa.Http/Extensions/StringValueExtensions.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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]!); ;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)!;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue