using System; using System.Collections.Specialized; using System.IO; using System.Threading.Tasks; using EmbedIO.Utilities; using Swan; namespace EmbedIO { partial class HttpContextExtensions { private static readonly object FormDataKey = new object(); private static readonly object QueryDataKey = new object(); /// /// Asynchronously retrieves the request body as an array of s. /// /// The on which this method is called. /// A Task, representing the ongoing operation, /// whose result will be an array of s containing the request body. /// is . public static async Task GetRequestBodyAsByteArrayAsync(this IHttpContext @this) { using var buffer = new MemoryStream(); using var stream = @this.OpenRequestStream(); await stream.CopyToAsync(buffer, WebServer.StreamCopyBufferSize, @this.CancellationToken).ConfigureAwait(false); return buffer.ToArray(); } /// /// Asynchronously buffers the request body into a read-only . /// /// The on which this method is called. /// A Task, representing the ongoing operation, /// whose result will be a read-only containing the request body. /// is . public static async Task GetRequestBodyAsMemoryStreamAsync(this IHttpContext @this) => new MemoryStream( await GetRequestBodyAsByteArrayAsync(@this).ConfigureAwait(false), false); /// /// Asynchronously retrieves the request body as a string. /// /// The on which this method is called. /// A Task, representing the ongoing operation, /// whose result will be a representation of the request body. /// is . public static async Task GetRequestBodyAsStringAsync(this IHttpContext @this) { using var reader = @this.OpenRequestText(); return await reader.ReadToEndAsync().ConfigureAwait(false); } /// /// Asynchronously deserializes a request body, using the default request deserializer. /// As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON /// request parsing methods of version 2. /// /// The expected type of the deserialized data. /// The on which this method is called. /// A Task, representing the ongoing operation, /// whose result will be the deserialized data. /// is . public static Task GetRequestDataAsync(this IHttpContext @this) => RequestDeserializer.Default(@this); /// /// Asynchronously deserializes a request body, using the specified request deserializer. /// /// The expected type of the deserialized data. /// The on which this method is called. /// A used to deserialize the request body. /// A Task, representing the ongoing operation, /// whose result will be the deserialized data. /// is . /// is . public static Task GetRequestDataAsync(this IHttpContext @this,RequestDeserializerCallback deserializer) => Validate.NotNull(nameof(deserializer), deserializer)(@this); /// /// Asynchronously parses a request body in application/x-www-form-urlencoded format. /// /// The on which this method is called. /// A Task, representing the ongoing operation, /// whose result will be a read-only of form field names and values. /// is . /// /// This method may safely be called more than once for the same : /// it will return the same collection instead of trying to parse the request body again. /// public static async Task GetRequestFormDataAsync(this IHttpContext @this) { if (!@this.Items.TryGetValue(FormDataKey, out var previousResult)) { NameValueCollection result; try { using var reader = @this.OpenRequestText(); result = UrlEncodedDataParser.Parse(await reader.ReadToEndAsync().ConfigureAwait(false), false); } catch (Exception e) { @this.Items[FormDataKey] = e; throw; } @this.Items[FormDataKey] = result; return result; } switch (previousResult) { case NameValueCollection collection: return collection; case Exception exception: throw exception.RethrowPreservingStackTrace(); case null: throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestFormDataAsync)} is null."); default: throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestFormDataAsync)} is of unexpected type {previousResult.GetType().FullName}"); } } /// /// Parses a request URL query. Note that this is different from getting the property, /// in that fields without an equal sign are treated as if they have an empty value, instead of their keys being grouped /// as values of the null key. /// /// The on which this method is called. /// A read-only . /// is . /// /// This method may safely be called more than once for the same : /// it will return the same collection instead of trying to parse the request body again. /// public static NameValueCollection GetRequestQueryData(this IHttpContext @this) { if (!@this.Items.TryGetValue(QueryDataKey, out var previousResult)) { NameValueCollection result; try { result = UrlEncodedDataParser.Parse(@this.Request.Url.Query, false); } catch (Exception e) { @this.Items[FormDataKey] = e; throw; } @this.Items[FormDataKey] = result; return result; } switch (previousResult) { case NameValueCollection collection: return collection; case Exception exception: throw exception.RethrowPreservingStackTrace(); case null: throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestQueryData)} is null."); default: throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestQueryData)} is of unexpected type {previousResult.GetType().FullName}"); } } } }