MultipartRequestService.cs 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Masuit.Tools.Core.AspNetCore;
  8. using Microsoft.AspNetCore.WebUtilities;
  9. using Microsoft.Extensions.DependencyInjection;
  10. using Microsoft.Extensions.Primitives;
  11. using Microsoft.Net.Http.Headers;
  12. namespace Masuit.Tools.AspNetCore.Extensions;
  13. [ServiceInject(ServiceLifetime.Scoped)]
  14. public class MultipartRequestService : IMultipartRequestService
  15. {
  16. public async Task<(Dictionary<string, StringValues>, byte[])> GetDataFromMultiPart(MultipartReader reader, CancellationToken cancellationToken)
  17. {
  18. var formAccumulator = new KeyValueAccumulator();
  19. var file = Array.Empty<byte>();
  20. MultipartSection section;
  21. while ((section = await reader.ReadNextSectionAsync(cancellationToken)) != null)
  22. {
  23. if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
  24. {
  25. continue;
  26. }
  27. if (contentDisposition.IsFormDisposition())
  28. {
  29. formAccumulator = await AccumulateForm(formAccumulator, section, contentDisposition);
  30. }
  31. else if (contentDisposition.IsFileDisposition())
  32. {
  33. // you will want to replace all of this because it copies the file into a memory stream. We don't want that.
  34. await using var memoryStream = new MemoryStream();
  35. await section.Body.CopyToAsync(memoryStream, cancellationToken);
  36. file = memoryStream.ToArray();
  37. }
  38. }
  39. return (formAccumulator.GetResults(), file);
  40. }
  41. private Encoding GetEncoding(MultipartSection section)
  42. {
  43. var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out var mediaType);
  44. // UTF-7 is insecure and shouldn't be honored. UTF-8 succeeds in
  45. // most cases.
  46. if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
  47. {
  48. return Encoding.UTF8;
  49. }
  50. return mediaType.Encoding;
  51. }
  52. private async Task<KeyValueAccumulator> AccumulateForm(KeyValueAccumulator formAccumulator, MultipartSection section, ContentDispositionHeaderValue contentDisposition)
  53. {
  54. var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name).Value;
  55. using var streamReader = new StreamReader(section.Body, GetEncoding(section), true, 1024, true);
  56. {
  57. var value = await streamReader.ReadToEndAsync();
  58. if (string.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
  59. {
  60. value = string.Empty;
  61. }
  62. formAccumulator.Append(key, value);
  63. if (formAccumulator.ValueCount > FormReader.DefaultValueCountLimit)
  64. {
  65. throw new InvalidDataException($"Form key count limit {FormReader.DefaultValueCountLimit} exceeded.");
  66. }
  67. }
  68. return formAccumulator;
  69. }
  70. }