Use ContentDisposition class to parse and create content disposition

This commit is contained in:
Sipke Schoorstra 2023-09-16 11:43:34 +02:00
parent bb2fa55d0e
commit eb88e40efa
3 changed files with 37 additions and 12 deletions

View file

@ -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<IContentTypeProvider>() ?? new FileExtensionContentTypeProvider();
return provider.TryGetContentType(filename, out var contentType) ? contentType : "application/octet-stream";
var provider = context.GetRequiredService<IContentTypeProvider>();
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)

View file

@ -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;
/// <inheritdoc />
public UrlDownloadableProvider(IFileDownloader fileDownloader)
public UrlDownloadableProvider(IFileDownloader fileDownloader, IContentTypeProvider contentTypeProvider)
{
_fileDownloader = fileDownloader;
_contentTypeProvider = contentTypeProvider;
}
/// <inheritdoc />
@ -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="?(?<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;
}
}

View file

@ -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.
/// </summary>
public Func<IServiceProvider, IHttpEndpointWorkflowFaultHandler> HttpEndpointWorkflowFaultHandler { get; set; } = sp => sp.GetRequiredService<DefaultHttpEndpointWorkflowFaultHandler>();
/// <summary>
/// A delegate to configure the <see cref="IContentTypeProvider"/>.
/// </summary>
public Func<IServiceProvider, IContentTypeProvider> ContentTypeProvider { get; set; } = _ => new FileExtensionContentTypeProvider();
/// <summary>
/// A delegate to configure the <see cref="HttpClient"/> used when by the <see cref="FlowSendHttpRequest"/> activity.
@ -128,6 +134,7 @@ public class HttpFeature : FeatureBase
.AddSingleton<IAbsoluteUrlProvider, DefaultAbsoluteUrlProvider>()
.AddSingleton<IHttpBookmarkProcessor, HttpBookmarkProcessor>()
.AddSingleton<IRouteTableUpdater, DefaultRouteTableUpdater>()
.AddSingleton(ContentTypeProvider)
.AddNotificationHandlersFrom<UpdateRouteTable>()
.AddHttpContextAccessor()