diff --git a/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs b/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs index db2424a5c..9a612fc52 100644 --- a/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs +++ b/src/modules/Elsa.Http/Activities/WriteFileHttpResponse.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using Elsa.Extensions; using Elsa.Http.Contracts; using Elsa.Http.Models; @@ -8,6 +9,7 @@ using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Exceptions; using Elsa.Workflows.Core.Models; using FluentStorage.Blobs; +using FluentStorage.Utils.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -141,10 +143,11 @@ public class WriteFileHttpResponse : Activity try { - // Send the zip stream the temporary file back to the client. + // Send the temporary file back to the client. var contentType = zipBlob.Metadata["ContentType"]; var downloadAsFilename = zipBlob.Metadata["Filename"]; - var eTag = $"\"{zipBlob.LastModificationTime?.ToString("O")}\""; + var hash = ComputeHash(zipStream); + var eTag = $"\"{hash}\""; var eTagHeaderValue = new EntityTagHeaderValue(eTag); await SendFileStream(context, httpContext, zipStream, contentType, downloadAsFilename, eTagHeaderValue); @@ -162,6 +165,16 @@ public class WriteFileHttpResponse : Activity } } + private string ComputeHash(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + var bytes = stream.ToByteArray()!; + using var md5Hash = MD5.Create(); + var hash = md5Hash.ComputeHash(bytes); + stream.Seek(0, SeekOrigin.Begin); + return Convert.ToBase64String(hash); + } + private async Task<(Blob, Stream, Func)> GenerateZipFileAsync(ActivityExecutionContext context, HttpContext httpContext, ICollection>> downloadables) { var cancellationToken = context.CancellationToken; diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs index a22fac1e6..1371a5cfc 100644 --- a/src/modules/Elsa.Http/Features/HttpFeature.cs +++ b/src/modules/Elsa.Http/Features/HttpFeature.cs @@ -27,6 +27,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Elsa.Http.Features; @@ -71,9 +72,10 @@ public class HttpFeature : FeatureBase /// /// A delegate to configure the . /// - public Func FileCache { get; set; } = _ => + public Func FileCache { get; set; } = sp => { - var blobStorage = StorageFactory.Blobs.DirectoryFiles(Path.GetTempPath()); + var options = sp.GetRequiredService>().Value; + var blobStorage = StorageFactory.Blobs.DirectoryFiles(options.LocalCacheDirectory); return new BlobFileCacheStorageProvider(blobStorage); }; diff --git a/src/modules/Elsa.Http/Options/HttpFileCacheOptions.cs b/src/modules/Elsa.Http/Options/HttpFileCacheOptions.cs index 2aa18777e..d6760ae64 100644 --- a/src/modules/Elsa.Http/Options/HttpFileCacheOptions.cs +++ b/src/modules/Elsa.Http/Options/HttpFileCacheOptions.cs @@ -9,4 +9,9 @@ public class HttpFileCacheOptions /// The time to live for cached files. /// public TimeSpan TimeToLive { get; set; } = TimeSpan.FromDays(7); + + /// + /// The local cache directory. Defaults to the system's temp directory. + /// + public string LocalCacheDirectory { get; set; } = Path.GetTempPath(); } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Services/ZipManager.cs b/src/modules/Elsa.Http/Services/ZipManager.cs index 559a6a2cc..a7c9f0324 100644 --- a/src/modules/Elsa.Http/Services/ZipManager.cs +++ b/src/modules/Elsa.Http/Services/ZipManager.cs @@ -39,7 +39,7 @@ internal class ZipManager CancellationToken cancellationToken = default) { // Create a temporary file. - var tempFilePath = Path.GetTempFileName(); + var tempFilePath = GetTempFilePath(); // Create a zip archive from the downloadables. await CreateZipArchiveAsync(tempFilePath, downloadables, cancellationToken); @@ -54,7 +54,7 @@ internal class ZipManager var zipStream = File.OpenRead(tempFilePath); return (zipBlob, zipStream, () => Cleanup(tempFilePath)); } - + /// /// Loads a cached zip blob for the specified download correlation ID. /// @@ -174,6 +174,13 @@ internal class ZipManager return (downloadAsFilename, contentType); } + private string GetTempFilePath() + { + var tempFileName = Path.GetRandomFileName(); + var tempFilePath = Path.Combine(_fileCacheOptions.Value.LocalCacheDirectory, tempFileName); + return tempFilePath; + } + private void Cleanup(string filePath) { try