Skip to content

Commit 931d242

Browse files
committed
Merge remote-tracking branch 'origin/develop/3.5.0' into develop/3.6.0
2 parents df8a0f0 + ed14a1e commit 931d242

File tree

19 files changed

+346
-78
lines changed

19 files changed

+346
-78
lines changed

Directory.Packages.props

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
</PropertyGroup>
181181
<PropertyGroup>
182182
<ElsaStudioVersion>3.5.0-preview.1092</ElsaStudioVersion>
183+
<MicrosoftVersion>9.0.7</MicrosoftVersion>
183184
</PropertyGroup>
184185
<ItemGroup>
185186
<PackageVersion Include="Antlr4.Runtime.Standard" Version="4.13.1"/>
@@ -209,7 +210,7 @@
209210
<PackageVersion Include="DistributedLock.FileSystem" Version="1.0.3"/>
210211
<PackageVersion Include="DistributedLock.Postgres" Version="1.3.0"/>
211212
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3"/>
212-
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="9.0.6"/>
213+
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="$(MicrosoftVersion)"/>
213214
<PackageVersion Include="Elsa.Studio" Version="$(ElsaStudioVersion)"/>
214215
<PackageVersion Include="Elsa.Studio.Agents" Version="$(ElsaStudioVersion)"/>
215216
<PackageVersion Include="Elsa.Studio.Core.BlazorWasm" Version="$(ElsaStudioVersion)"/>
@@ -234,7 +235,7 @@
234235
<PackageVersion Include="Humanizer.Core" Version="2.14.1"/>
235236
<PackageVersion Include="IronCompress" Version="1.6.3"/>
236237
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0"/>
237-
<PackageVersion Include="Jint" Version="4.2.2"/>
238+
<PackageVersion Include="Jint" Version="4.3.0"/>
238239
<PackageVersion Include="LinqKit.Core" Version="1.2.8"/>
239240
<PackageVersion Include="MailKit" Version="4.12.1"/>
240241
<PackageVersion Include="MassTransit" Version="8.4.1"/>
@@ -293,45 +294,45 @@
293294
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.5"/>
294295
<PackageVersion Include="System.Net.Http" Version="4.3.4"/>
295296
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1"/>
296-
<PackageVersion Include="System.Formats.Asn1" Version="9.0.6"/>
297-
<PackageVersion Include="System.Text.Json" Version="9.0.6"/>
297+
<PackageVersion Include="System.Formats.Asn1" Version="$(MicrosoftVersion)"/>
298+
<PackageVersion Include="System.Text.Json" Version="$(MicrosoftVersion)"/>
298299
<PackageVersion Include="Testcontainers" Version="4.5.0"/>
299300
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.5.0"/>
300301
<PackageVersion Include="Testcontainers.RabbitMq" Version="4.5.0"/>
301302
<PackageVersion Include="Testcontainers.Redis" Version="4.5.0"/>
302303
<PackageVersion Include="ThrottleDebounce" Version="2.0.1"/>
303304
<PackageVersion Include="WebhooksCore" Version="0.0.1"/>
304305
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0"/>
305-
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.6"/>
306-
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.6"/>
307-
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.6"/>
308-
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.6"/>
309-
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.6"/>
310-
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="9.0.6"/>
311-
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.6"/>
312-
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.6"/>
313-
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.6"/>
314-
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.6"/>
315-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6"/>
316-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.6"/>
317-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6"/>
318-
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6"/>
319-
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.6"/>
320-
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.6"/>
321-
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.6"/>
322-
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.6"/>
323-
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.6"/>
324-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6"/>
325-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.6"/>
326-
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="9.0.6"/>
327-
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.6"/>
328-
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.6"/>
329-
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="9.0.6"/>
330-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6"/>
331-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6"/>
332-
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.6"/>
333-
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.6"/>
334-
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.6"/>
306+
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="$(MicrosoftVersion)"/>
307+
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="$(MicrosoftVersion)"/>
308+
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="$(MicrosoftVersion)"/>
309+
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="$(MicrosoftVersion)"/>
310+
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="$(MicrosoftVersion)"/>
311+
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="$(MicrosoftVersion)"/>
312+
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftVersion)"/>
313+
<PackageVersion Include="Microsoft.Data.Sqlite" Version="$(MicrosoftVersion)"/>
314+
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="$(MicrosoftVersion)"/>
315+
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="$(MicrosoftVersion)"/>
316+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(MicrosoftVersion)"/>
317+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftVersion)"/>
318+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(MicrosoftVersion)"/>
319+
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(MicrosoftVersion)"/>
320+
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="$(MicrosoftVersion)"/>
321+
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="$(MicrosoftVersion)"/>
322+
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftVersion)"/>
323+
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftVersion)"/>
324+
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftVersion)"/>
325+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftVersion)"/>
326+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftVersion)"/>
327+
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftVersion)"/>
328+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftVersion)"/>
329+
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(MicrosoftVersion)"/>
330+
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="$(MicrosoftVersion)"/>
331+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(MicrosoftVersion)"/>
332+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)"/>
333+
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftVersion)"/>
334+
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(MicrosoftVersion)"/>
335+
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="$(MicrosoftVersion)"/>
335336
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.6.0"/>
336337
<PackageVersion Include="Microsoft.Extensions.Resilience" Version="9.6.0"/>
337338
<PackageVersion Include="MySql.Data" Version="9.3.0"/>

src/modules/Elsa.Common/Entities/Entity.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public abstract class Entity
88
/// <summary>
99
/// Gets or sets the ID of this entity.
1010
/// </summary>
11-
public string Id { get; set; } = default!;
11+
public string Id { get; set; } = null!;
1212

1313
/// <summary>
1414
/// Gets or sets the ID of the tenant that own this entity.

src/modules/Elsa.Expressions.JavaScript/Handlers/ConfigureEngineWithCommonFunctions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken can
5959
engine.SetValue("bytesFromBase64", (Func<string, byte[]>)(value => Convert.FromBase64String(value)));
6060
engine.SetValue("stringToBase64", (Func<string, string>)(value => Convert.ToBase64String(Encoding.UTF8.GetBytes(value))));
6161
engine.SetValue("stringFromBase64", (Func<string, string>)(value => Encoding.UTF8.GetString(Convert.FromBase64String(value))));
62+
engine.SetValue("streamToBytes", (Func<Stream, byte[]>)(value => StreamToBytes(value)));
63+
engine.SetValue("streamToBase64", (Func<Stream, string>)(value => Convert.ToBase64String(StreamToBytes(value))));
6264

6365
// Deprecated, use newGuidString instead.
6466
engine.SetValue("getGuidString", (Func<string>)(() => Guid.NewGuid().ToString()));
@@ -67,7 +69,7 @@ public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken can
6769
engine.SetValue("getShortGuid", (Func<string>)(() => Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "")));
6870
return Task.CompletedTask;
6971
}
70-
72+
7173
private string Serialize(object value)
7274
{
7375
return JsonSerializer.Serialize(value, _jsonSerializerOptions);
@@ -82,4 +84,11 @@ private static JsonSerializerOptions CreateJsonSerializerOptions()
8284
options.Converters.Add(new JsonStringEnumConverter());
8385
return options;
8486
}
87+
88+
private byte[] StreamToBytes(Stream stream)
89+
{
90+
using var memoryStream = new MemoryStream();
91+
stream.CopyTo(memoryStream);
92+
return memoryStream.ToArray();
93+
}
8594
}

src/modules/Elsa.Expressions.JavaScript/Providers/CommonFunctionsDefinitionProvider.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ protected override IEnumerable<FunctionDefinition> GetFunctionDefinitions(TypeDe
136136
.Name("stringToBase64")
137137
.Parameter("value", "string")
138138
.ReturnType("string"));
139+
140+
yield return CreateFunctionDefinition(builder => builder
141+
.Name("streamToBytes")
142+
.Parameter("value", "Stream")
143+
.ReturnType("Byte[]"));
144+
145+
yield return CreateFunctionDefinition(builder => builder
146+
.Name("streamToBase64")
147+
.Parameter("value", "Stream")
148+
.ReturnType("string"));
139149

140150
if (!options.Value.DisableWrappers)
141151
{
@@ -151,7 +161,7 @@ protected override IEnumerable<FunctionDefinition> GetFunctionDefinitions(TypeDe
151161

152162
// set{Variable}.
153163
yield return CreateFunctionDefinition(builder => builder.Name($"set{pascalName}").Parameter("value", typeAlias));
154-
}
164+
}
155165
}
156166
}
157167
}

src/modules/Elsa.IO.Compression/Activities/CreateZipArchive.cs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private async Task<Stream> CreateZipStreamFromEntries(
8080

8181
try
8282
{
83-
using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, leaveOpen: true);
83+
using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Update, leaveOpen: true);
8484
var entryIndex = 0;
8585

8686
var compressionLevel = CompressionLevel.Get(context);
@@ -122,19 +122,87 @@ private static async Task ProcessZipEntry(
122122
CompressionLevel compressionLevel)
123123
{
124124
var binaryContent = await resolver.ResolveAsync(entryContent, context.CancellationToken);
125-
126-
var entryName = binaryContent.Name?.GetNameAndExtension()
125+
126+
var entryName = binaryContent.Name?.GetNameAndExtension()
127127
?? string.Format(DefaultEntryNameFormat, entryIndex + 1);
128128

129+
// Get a unique name following Windows convention
130+
entryName = GetUniqueEntryName(zipArchive, entryName);
131+
129132
var archiveEntry = zipArchive.CreateEntry(entryName, compressionLevel);
130133

131134
await using var entryStream = archiveEntry.Open();
132135
await binaryContent.Stream.CopyToAsync(entryStream, context.CancellationToken);
133136
await entryStream.FlushAsync(context.CancellationToken);
134-
137+
135138
if (entryContent is not Stream)
136139
{
137140
await binaryContent.Stream.DisposeAsync();
138141
}
139142
}
143+
144+
private static string GetUniqueEntryName(ZipArchive zipArchive, string originalName)
145+
{
146+
var filenameWithoutExtension = Path.GetFileNameWithoutExtension(originalName);
147+
var extension = Path.GetExtension(originalName);
148+
149+
var originalExists = false;
150+
var highestIndex = 0;
151+
152+
foreach (var entry in zipArchive.Entries)
153+
{
154+
if (!entry.Name.Equals(originalName, StringComparison.OrdinalIgnoreCase))
155+
{
156+
continue;
157+
}
158+
159+
originalExists = true;
160+
161+
var entryNameWithoutExtension = Path.GetFileNameWithoutExtension(entry.Name);
162+
var entryExtension = Path.GetExtension(entry.Name);
163+
164+
// Only process entries with the same extension
165+
if (!entryExtension.Equals(extension, StringComparison.OrdinalIgnoreCase))
166+
continue;
167+
168+
// Check if this entry follows our naming pattern
169+
highestIndex = HighestEntryNameIndex(entryNameWithoutExtension, filenameWithoutExtension, highestIndex);
170+
}
171+
172+
if (!originalExists)
173+
{
174+
return originalName;
175+
}
176+
177+
return $"{filenameWithoutExtension}({highestIndex + 1}){extension}";
178+
}
179+
180+
private static int HighestEntryNameIndex(string entryNameWithoutExtension, string filenameWithoutExtension,
181+
int highestIndex)
182+
{
183+
if (!entryNameWithoutExtension.StartsWith(filenameWithoutExtension, StringComparison.OrdinalIgnoreCase) ||
184+
entryNameWithoutExtension.Length <= filenameWithoutExtension.Length ||
185+
entryNameWithoutExtension[filenameWithoutExtension.Length] != '(')
186+
{
187+
return highestIndex;
188+
}
189+
190+
// Extract the number between parentheses
191+
var closingParenIndex = entryNameWithoutExtension.LastIndexOf(')');
192+
if (closingParenIndex <= filenameWithoutExtension.Length + 1)
193+
{
194+
return highestIndex;
195+
}
196+
197+
var indexStr = entryNameWithoutExtension.Substring(
198+
filenameWithoutExtension.Length + 1,
199+
closingParenIndex - filenameWithoutExtension.Length - 1);
200+
201+
if (int.TryParse(indexStr, out var index))
202+
{
203+
highestIndex = Math.Max(highestIndex, index);
204+
}
205+
206+
return highestIndex;
207+
}
140208
}

src/modules/Elsa.IO.Compression/Services/Strategies/ZipEntryContentStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public async Task<BinaryContent> ResolveAsync(object content, CancellationToken
3636
var innerContentName = innerContent.Name?.GetNameAndExtension();
3737
var innerContentExtension = Path.GetExtension(innerContentName);
3838
innerContent.Name = !string.IsNullOrWhiteSpace(innerContentExtension)
39-
? zipEntry.EntryName + innerContentExtension
39+
? Path.HasExtension(zipEntry.EntryName) ? zipEntry.EntryName : zipEntry.EntryName + innerContentExtension
4040
: innerContent.Name;
4141

4242
return innerContent;

src/modules/Elsa.IO/Extensions/ContentTypeExtensions.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,62 @@ public static string GetFileExtension(this string filePath)
8585
return Path.GetExtension(filePath).ToLowerInvariant();
8686
}
8787

88+
public static bool IsBase64String(this string s)
89+
{
90+
if (string.IsNullOrWhiteSpace(s))
91+
return false;
92+
93+
s = s.Trim();
94+
95+
// Length must be divisible by 4
96+
if (s.Length % 4 != 0)
97+
return false;
98+
99+
// Check padding position and count
100+
var paddingIndex = s.IndexOf('=');
101+
102+
switch (paddingIndex)
103+
{
104+
// Padding cannot be at index 0
105+
case 0:
106+
// Padding must be at the end
107+
case > 0 when paddingIndex < s.Length - 2:
108+
// All characters after first '=' must also be '='
109+
case > 0 when s[paddingIndex..].Any(c => c != '='):
110+
return false;
111+
}
112+
113+
// Check for valid Base64 characters
114+
for (var i = 0; i < paddingIndex; i++)
115+
{
116+
var c = s[i];
117+
var isValid =
118+
c is >= 'A' and <= 'Z' ||
119+
c is >= 'a' and <= 'z' ||
120+
c is >= '0' and <= '9' ||
121+
c == '+' || c == '/';
122+
123+
if (!isValid)
124+
return false;
125+
}
126+
127+
// Additional check for short strings that are just lowercase+numbers
128+
// This catches "whatever" and similar false positives
129+
if (s.Length <= 10 && s.All(c => char.IsLower(c) || char.IsDigit(c)))
130+
return false;
131+
132+
// Try actual decoding
133+
try
134+
{
135+
_ = Convert.FromBase64String(s);
136+
return true;
137+
}
138+
catch
139+
{
140+
return false;
141+
}
142+
}
143+
88144
private static string DetermineExtensionFromMimeType(string mimeType)
89145
{
90146
if (mimeType.Contains("/pdf"))

src/modules/Elsa.IO/Services/Strategies/Base64ContentStrategy.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,8 @@ public Task<BinaryContent> ResolveAsync(object content, CancellationToken cancel
5151

5252
private static bool IsBase64String(string base64)
5353
{
54-
if (IsUriDataBase64String(base64))
55-
{
56-
return true;
57-
}
58-
59-
var buffer = new Span<byte>(new byte[base64.Length]);
60-
return Convert.TryFromBase64String(base64, buffer , out _);
54+
return IsUriDataBase64String(base64)
55+
|| base64.IsBase64String();
6156
}
6257

6358
private static bool IsUriDataBase64String(string base64)

src/modules/Elsa.Workflows.Core/VariableStorageDrivers/WorkflowInstanceStorageDriver.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Elsa.Expressions.Helpers;
55
using Elsa.Extensions;
66
using JetBrains.Annotations;
7+
using Microsoft.Extensions.Logging;
78

89
namespace Elsa.Workflows;
910

@@ -12,7 +13,7 @@ namespace Elsa.Workflows;
1213
/// </summary>
1314
[Display(Name = "Workflow Instance")]
1415
[UsedImplicitly]
15-
public class WorkflowInstanceStorageDriver(IPayloadSerializer payloadSerializer) : IStorageDriver
16+
public class WorkflowInstanceStorageDriver(IPayloadSerializer payloadSerializer, ILogger<WorkflowInstanceStorageDriver> logger) : IStorageDriver
1617
{
1718
/// <summary>
1819
/// The key used to store the variables in the workflow state.
@@ -29,8 +30,18 @@ public ValueTask WriteAsync(string id, object value, StorageDriverContext contex
2930
{
3031
UpdateVariablesDictionary(context, dictionary =>
3132
{
32-
var node = JsonSerializer.SerializeToNode(value);
33-
dictionary[id] = node;
33+
try
34+
{
35+
var node = JsonSerializer.SerializeToNode(value);
36+
dictionary[id] = node;
37+
}
38+
catch (Exception ex) when (ex is JsonException or NotSupportedException or ObjectDisposedException)
39+
{
40+
logger.LogWarning(ex, "Failed to serialize variable '{VariableId}' of type '{VariableType}' for workflow instance storage. The variable will be skipped.",
41+
id, value?.GetType().FullName ?? "null");
42+
43+
dictionary.Remove(id);
44+
}
3445
});
3546
return ValueTask.CompletedTask;
3647
}

0 commit comments

Comments
 (0)