Skip to content

Commit 463c052

Browse files
authored
Fix blob URL handling for large messages with special characters in instanceId (#1204)
1 parent 80d45c2 commit 463c052

File tree

4 files changed

+51
-8
lines changed

4 files changed

+51
-8
lines changed

src/DurableTask.AzureStorage/MessageManager.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,9 @@ private async Task<string> DownloadAndDecompressAsBytesAsync(Blob blob, Cancella
255255

256256
public string GetBlobUrl(string blobName)
257257
{
258-
return Uri.UnescapeDataString(this.blobContainer.GetBlobReference(blobName).Uri.AbsoluteUri);
258+
string baseUri = this.blobContainer.GetBlobContainerUri().ToString();
259+
260+
return $"{baseUri}/{EscapeBlobNamePreservingSlashes(blobName)}";
259261
}
260262

261263
public MessageFormatFlags GetMessageFormatFlags(MessageData messageData)
@@ -304,6 +306,12 @@ public async Task<int> DeleteLargeMessageBlobs(string sanitizedInstanceId, Cance
304306

305307
return storageOperationCount;
306308
}
309+
310+
/// Escapes a blob name for safe inclusion in a URI while preserving path structure.
311+
static string EscapeBlobNamePreservingSlashes(string blobName)
312+
{
313+
return string.Join("/", blobName.Split('/').Select(Uri.EscapeDataString));
314+
}
307315
}
308316

309317
#if NETSTANDARD2_0

src/DurableTask.AzureStorage/Storage/BlobContainer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ public Task RenewLeaseAsync(string leaseId, CancellationToken cancellationToken
108108
.DecorateFailure();
109109
}
110110

111+
public Uri GetBlobContainerUri()
112+
{
113+
return this.blobContainerClient.Uri;
114+
}
115+
111116
static bool IsHnsFolder(BlobItem item)
112117
{
113118
// Check the optional "hdi_isfolder" value in the metadata to determine whether

test/DurableTask.AzureStorage.Tests/AzureStorageScenarioTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,35 @@ await ValidateLargeMessageBlobUrlAsync(
16001600
}
16011601
}
16021602

1603+
/// <summary>
1604+
/// End-to-end test that validates large (>60KB) messages stored in blob storage can be retrieved successfully,
1605+
/// when the instance ID includes special characters like '|' that can affect blob URL encoding.
1606+
/// </summary>
1607+
[TestMethod]
1608+
public async Task LargeMessage_WithEscapedInstanceId_CanBeStoredAndFetchedSuccessfully()
1609+
{
1610+
// Genereates a random large message.
1611+
const int largeMessageSize = 60 * 1024;
1612+
1613+
using (TestOrchestrationHost host = TestHelpers.GetTestOrchestrationHost(enableExtendedSessions: false))
1614+
{
1615+
await host.StartAsync();
1616+
1617+
string message = this.GenerateMediumRandomStringPayload(largeMessageSize, utf8ByteSize: 1, utf16ByteSize: 2).ToString();
1618+
1619+
// Use an instanceId that contains special characters which must be escaped in URIs
1620+
string id = "test|123:with white spcae";
1621+
var client = await host.StartOrchestrationAsync(typeof(Orchestrations.Echo), input:message, instanceId: id);
1622+
var status = await client.WaitForCompletionAsync(TimeSpan.FromMinutes(2));
1623+
1624+
Assert.AreEqual(OrchestrationStatus.Completed, status?.OrchestrationStatus);
1625+
1626+
// Verify that the output matches the original message (the blob was successfully downloaded and not returned as a URL)
1627+
StringAssert.Contains(status.Output.ToString(), message);
1628+
await host.StopAsync();
1629+
}
1630+
}
1631+
16031632
[TestMethod]
16041633
public async Task TagsAreAvailableInOrchestrationState()
16051634
{

test/DurableTask.AzureStorage.Tests/MessageManagerTests.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
#nullable enable
1414
namespace DurableTask.AzureStorage.Tests
1515
{
16-
using System;
17-
using System.Collections.Generic;
1816
using DurableTask.AzureStorage.Storage;
1917
using DurableTask.Core.History;
2018
using Microsoft.VisualStudio.TestTools.UnitTesting;
2119
using Newtonsoft.Json;
20+
using System;
21+
using System.Collections.Generic;
2222

2323
[TestClass]
2424
public class MessageManagerTests
@@ -70,10 +70,10 @@ public void DeserializesCustomTypes()
7070
}
7171

7272
[DataTestMethod]
73-
[DataRow("blob.bin")]
74-
[DataRow("@#$%!")]
75-
[DataRow("foo/bar/b@z.tar.gz")]
76-
public void GetBlobUrlUnescaped(string blob)
73+
[DataRow("blob.bin", "blob.bin")]
74+
[DataRow("@#$%!", "%40%23%24%25%21")]
75+
[DataRow("foo/bar/b@z.tar.gz", "foo/bar/b%40z.tar.gz")]
76+
public void GetBlobUrlEscaped(string blob, string blobUrl)
7777
{
7878
var settings = new AzureStorageOrchestrationServiceSettings
7979
{
@@ -82,7 +82,8 @@ public void GetBlobUrlUnescaped(string blob)
8282

8383
const string container = "@entity12345";
8484
var manager = new MessageManager(settings, new AzureStorageClient(settings), container);
85-
var expected = $"http://127.0.0.1:10000/devstoreaccount1/{container}/{blob}";
85+
86+
var expected = $"http://127.0.0.1:10000/devstoreaccount1/{container}/{blobUrl}";
8687
Assert.AreEqual(expected, manager.GetBlobUrl(blob));
8788
}
8889

0 commit comments

Comments
 (0)