From eb88e40efabd8fca5fd17b4505b89a24878eb4a7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 16 Sep 2023 11:43:34 +0200 Subject: [PATCH] Use ContentDisposition class to parse and create content disposition --- .../Activities/WriteFileHttpResponse.cs | 19 +++++++++++---- .../UrlDownloadableProvider.cs | 23 ++++++++++++------- src/modules/Elsa.Http/Features/HttpFeature.cs | 7 ++++++ 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs b/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs index 3c2837b94..fc7d005c0 100644 --- a/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs +++ b/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs @@ -1,5 +1,6 @@ using System.IO.Compression; using System.Net; +using System.Net.Http.Headers; using Elsa.Extensions; using Elsa.Http.Contracts; using Elsa.Http.Models; @@ -101,7 +102,7 @@ public class WriteFileHttpResponse : Activity filename = !string.IsNullOrWhiteSpace(filename) ? filename : !string.IsNullOrWhiteSpace(downloadable.Filename) ? downloadable.Filename : "file.bin"; contentType = !string.IsNullOrWhiteSpace(contentType) ? contentType : !string.IsNullOrWhiteSpace(downloadable.ContentType) ? downloadable.ContentType : GetContentType(context, filename); response.ContentType = contentType; - response.Headers.Add("Content-Disposition", $"attachment; filename=\"{filename}\""); + response.Headers.Add("Content-Disposition", CreateContentDisposition(filename)); await downloadable.Stream.CopyToAsync(response.Body); } @@ -130,7 +131,7 @@ public class WriteFileHttpResponse : Activity memoryStream.Position = 0; response.ContentType = contentType; - response.Headers.Add("Content-Disposition", $"attachment; filename=\"{filename}\""); + response.Headers.Add("Content-Disposition", CreateContentDisposition(filename)); await memoryStream.CopyToAsync(response.Body); } @@ -145,8 +146,18 @@ public class WriteFileHttpResponse : Activity private string GetContentType(ActivityExecutionContext context, string filename) { - var provider = context.GetService() ?? new FileExtensionContentTypeProvider(); - return provider.TryGetContentType(filename, out var contentType) ? contentType : "application/octet-stream"; + var provider = context.GetRequiredService(); + return provider.TryGetContentType(filename, out var contentType) ? contentType : System.Net.Mime.MediaTypeNames.Application.Octet; + } + + private static string CreateContentDisposition(string filename) + { + var contentDisposition = new System.Net.Mime.ContentDisposition + { + FileName = filename + }; + + return contentDisposition.ToString(); } private async ValueTask OnResumeAsync(ActivityExecutionContext context) diff --git a/src/modules/Elsa.Http/DownloadableProviders/UrlDownloadableProvider.cs b/src/modules/Elsa.Http/DownloadableProviders/UrlDownloadableProvider.cs index 008736973..e9527a760 100644 --- a/src/modules/Elsa.Http/DownloadableProviders/UrlDownloadableProvider.cs +++ b/src/modules/Elsa.Http/DownloadableProviders/UrlDownloadableProvider.cs @@ -3,6 +3,7 @@ using Elsa.Http.Abstractions; using Elsa.Http.Contexts; using Elsa.Http.Contracts; using Elsa.Http.Models; +using Microsoft.AspNetCore.StaticFiles; namespace Elsa.Http.DownloadableProviders; @@ -12,11 +13,13 @@ namespace Elsa.Http.DownloadableProviders; public class UrlDownloadableProvider : DownloadableProviderBase { private readonly IFileDownloader _fileDownloader; + private readonly IContentTypeProvider _contentTypeProvider; /// - public UrlDownloadableProvider(IFileDownloader fileDownloader) + public UrlDownloadableProvider(IFileDownloader fileDownloader, IContentTypeProvider contentTypeProvider) { _fileDownloader = fileDownloader; + _contentTypeProvider = contentTypeProvider; } /// @@ -28,11 +31,11 @@ public class UrlDownloadableProvider : DownloadableProviderBase var url = context.Content is string s ? new Uri(s) : (Uri)context.Content; var cancellationToken = context.CancellationToken; var response = await _fileDownloader.DownloadAsync(url, cancellationToken); - var fileName = GetFilename(response) ?? url.Segments.Last(); + var filename = GetFilename(response) ?? url.Segments.Last(); var stream = await response.Content.ReadAsStreamAsync(cancellationToken); - var contentType = response.Content.Headers.ContentType?.MediaType ?? "application/octet-stream"; + var contentType = response.Content.Headers.ContentType?.MediaType ?? GetContentType(filename); - return new Downloadable(stream, fileName, contentType); + return new Downloadable(stream, filename, contentType); } private static string? GetFilename(HttpResponseMessage response) @@ -40,10 +43,14 @@ public class UrlDownloadableProvider : DownloadableProviderBase if (!response.Content.Headers.TryGetValues("Content-Disposition", out var values)) return null; - var contentDisposition = string.Join("", values); - var match = Regex.Match(contentDisposition, """filename="?(?[^";]*)"?"""); - - return match.Success ? match.Groups["filename"].Value : null; + var contentDispositionString = string.Join("", values); + var contentDisposition = new System.Net.Mime.ContentDisposition(contentDispositionString); + return contentDisposition.FileName; + } + + private string GetContentType(string filename) + { + return _contentTypeProvider.TryGetContentType(filename, out var contentType) ? contentType : System.Net.Mime.MediaTypeNames.Application.Octet; } } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs index c07bfa9a6..5820e5d96 100644 --- a/src/modules/Elsa.Http/Features/HttpFeature.cs +++ b/src/modules/Elsa.Http/Features/HttpFeature.cs @@ -22,6 +22,7 @@ using Elsa.Workflows.Management.Requests; using Elsa.Workflows.Management.Responses; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Http.Features; @@ -53,6 +54,11 @@ public class HttpFeature : FeatureBase /// A delegate that is invoked when an HTTP workflow faults. /// public Func HttpEndpointWorkflowFaultHandler { get; set; } = sp => sp.GetRequiredService(); + + /// + /// A delegate to configure the . + /// + public Func ContentTypeProvider { get; set; } = _ => new FileExtensionContentTypeProvider(); /// /// A delegate to configure the used when by the activity. @@ -128,6 +134,7 @@ public class HttpFeature : FeatureBase .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton(ContentTypeProvider) .AddNotificationHandlersFrom() .AddHttpContextAccessor()