diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 525bb344c4ed0..3212df5094fd8 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -151,7 +151,7 @@ - + diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs index 600764973f2e5..889f5675c93d6 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs @@ -242,18 +242,6 @@ internal static string InvalidConnectionString } } - /// - /// Looks up a localized string similar to The Event Hubs emulator is only available locally. The endpoint must reference to the local host.. - /// - internal static string InvalidEmulatorEndpoint - { - get - { - return ResourceManager.GetString("InvalidEmulatorEndpoint", resourceCulture); - } - } - - /// /// Looks up a localized string similar to The string has an invalid encoding format.. /// diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx index 4cdb0914155ea..11f9364633207 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx +++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx @@ -342,9 +342,6 @@ For troubleshooting information, see https://aka.ms/azsdk/net/eventhubs/exceptions/troubleshoot - - The Event Hubs emulator is only available locally. The endpoint must reference to the local host. - The buffered producer took too long to start. diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md b/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md index e681c113dc96c..7f3b273077be7 100755 --- a/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md +++ b/sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md @@ -1,16 +1,16 @@ # Release History -## 5.12.0-beta.1 (Unreleased) - -### Features Added - -### Breaking Changes +## 5.11.3 (2024-05-15) ### Bugs Fixed +- Fixed an error that caused connection strings using host names without a scheme to fail parsing and be considered invalid. + ### Other Changes -- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.6, which includes a bug fix for an internal `NullReferenceException` that would sometimes impact creating new links. _(see: [#258](https://github.com/azure/azure-amqp/issues/258))_ +- Removed the restriction that endpoints used with the development emulator had to resolve to a `localhost` variant. + +- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.7, which contains several bug fixes, including for an internal `NullReferenceException` that would sometimes impact creating new links. _(see: [#258](https://github.com/azure/azure-amqp/issues/258))_ ## 5.11.2 (2024-04-10) diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj b/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj index 3d57a798efffd..5adf68ae3a2f4 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj +++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj @@ -1,7 +1,7 @@ Azure Event Hubs is a highly scalable publish-subscribe service that can ingest millions of events per second and stream them to multiple consumers. This client library allows for both publishing and consuming events using Azure Event Hubs. For more information about Event Hubs, see https://azure.microsoft.com/en-us/services/event-hubs/ - 5.12.0-beta.1 + 5.11.3 5.11.2 Azure;Event Hubs;EventHubs;.NET;AMQP;IoT;$(PackageCommonTags) diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs index 388c74788caf1..0c5482decd9ae 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Runtime.CompilerServices; using System.Text; using Azure.Core; @@ -244,13 +245,6 @@ internal void Validate(string explicitEventHubName, { throw new ArgumentException(Resources.MissingConnectionInformation, connectionStringArgumentName); } - - // Ensure that the namespace reflects the local host when the development emulator is being used. - - if ((UseDevelopmentEmulator) && (!Endpoint.IsLoopback)) - { - throw new ArgumentException(Resources.InvalidEmulatorEndpoint, connectionStringArgumentName); - } } /// @@ -334,6 +328,17 @@ public static EventHubsConnectionStringProperties Parse(string connectionString) { endpointUri = null; } + else if (string.IsNullOrEmpty(endpointUri.Host) && (CountChar(':', value.AsSpan()) == 1)) + { + // If the host was empty after parsing and the value has a single port/scheme separator, + // then the parsing likely failed to recognize the host due to the lack of a scheme. Add + // an artificial scheme and try to parse again. + + if (!Uri.TryCreate(string.Concat(EventHubsEndpointScheme, value), UriKind.Absolute, out endpointUri)) + { + endpointUri = null; + } + } var endpointBuilder = endpointUri switch { @@ -400,5 +405,30 @@ public static EventHubsConnectionStringProperties Parse(string connectionString) return parsedValues; } + + /// + /// Counts the number of times a character occurs in a given span. + /// + /// + /// The span to evaluate. + /// The character to count. + /// + /// The number of times the occurs in . + /// + private static int CountChar(char value, + ReadOnlySpan span) + { + var count = 0; + + foreach (var character in span) + { + if (character == value) + { + ++count; + } + } + + return count; + } } } diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/EventHubsConnectionStringPropertiesTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/EventHubsConnectionStringPropertiesTests.cs index d3364a3124813..d4746f12b5a72 100644 --- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/EventHubsConnectionStringPropertiesTests.cs +++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/EventHubsConnectionStringPropertiesTests.cs @@ -347,7 +347,7 @@ public void ParseIgnoresUnknownTokens() [TestCase("amqp://test.endpoint.com")] [TestCase("http://test.endpoint.com")] [TestCase("https://test.endpoint.com:8443")] - public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue) + public void ParseAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue) { var connectionString = $"Endpoint={ endpointValue };EntityPath=dummy"; var parsed = EventHubsConnectionStringProperties.Parse(connectionString); @@ -371,7 +371,7 @@ public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue) /// /// [Test] - [TestCase("test.endpoint.com:443")] + [TestCase("test-endpoint|com:443")] [TestCase("notvalid=[broke]")] public void ParseDoesNotAllowAnInvalidEndpointFormat(string endpointValue) { @@ -402,6 +402,7 @@ public void ParseConsidersMissingValuesAsMalformed(string connectionString) /// method. /// /// + [Test] public void ParseDetectsDevelopmentEmulatorUse() { var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true"; @@ -416,6 +417,7 @@ public void ParseDetectsDevelopmentEmulatorUse() /// method. /// /// + [Test] public void ParseRespectsDevelopmentEmulatorValue() { var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=false"; @@ -425,6 +427,57 @@ public void ParseRespectsDevelopmentEmulatorValue() Assert.That(parsed.UseDevelopmentEmulator, Is.False, "The development emulator flag should have been unset."); } + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + [TestCase("localhost")] + [TestCase("localhost:9084")] + [TestCase("127.0.0.1")] + [TestCase("local.docker.com")] + [TestCase("local.docker.com:8080")] + [TestCase("www.fake.com")] + [TestCase("www.fake.com:443")] + public void ParseRespectsTheEndpointForDevelopmentEmulatorValue(string host) + { + var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true"; + var endpoint = new Uri(string.Concat(GetEventHubsEndpointScheme(), host)); + var parsed = EventHubsConnectionStringProperties.Parse(connectionString); + + Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match."); + Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match."); + Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set."); + } + + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + [TestCase("sb://localhost")] + [TestCase("http://localhost:9084")] + [TestCase("sb://local.docker.com")] + [TestCase("amqps://local.docker.com:8080")] + [TestCase("sb://www.fake.com")] + [TestCase("amqp://www.fake.com:443")] + public void ParseRespectsTheUrlFormatEndpointForDevelopmentEmulatorValue(string host) + { + var endpoint = new UriBuilder(host) + { + Scheme = GetEventHubsEndpointScheme() + }; + + var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true"; + var parsed = EventHubsConnectionStringProperties.Parse(connectionString); + + Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match."); + Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match."); + Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set."); + } + /// /// Verifies functionality of the /// method. @@ -657,33 +710,6 @@ public void ValidateAllowsSharedAccessSignatureAuthorization() Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access signature authorization."); } - /// - /// Verifies functionality of the - /// method. - /// - /// - [Test] - [TestCase("localhost", true)] - [TestCase("127.0.0.1", true)] - [TestCase("www.microsoft.com", false)] - [TestCase("fake.servicebus.windows.net", false)] - [TestCase("weirdname:8080", false)] - public void ValidateRequiresLocalEndpointForDevelopmentEmulator(string endpoint, - bool isValid) - { - var fakeConnection = $"Endpoint=sb://{ endpoint };SharedAccessSignature=[not_real];UseDevelopmentEmulator=true"; - var properties = EventHubsConnectionStringProperties.Parse(fakeConnection); - - if (isValid) - { - Assert.That(() => properties.Validate("fake", "dummy"), Throws.Nothing, "Validation should allow a local endpoint."); - } - else - { - Assert.That(() => properties.Validate("fake", "dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.InvalidEmulatorEndpoint), "Validation should enforce that the endpoint is a local address."); - } - } - /// /// Compares two instances for /// structural equality. diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md b/sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md index 3e1d861433d11..b82ea620b4eb1 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md @@ -8,8 +8,12 @@ ### Bugs Fixed +- Fixed an error that caused connection strings using host names without a scheme to fail parsing and be considered invalid. + ### Other Changes +- Updated the `Microsoft.Azure.Amqp` dependency to 2.6.7, which contains a fix for decoding messages with a null format code as the body. + ## 7.18.0-beta.1 (2024-05-08) ### Features Added diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ServiceBusConnectionStringProperties.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ServiceBusConnectionStringProperties.cs index 1b56fe418e66d..33c6ed1e7529d 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ServiceBusConnectionStringProperties.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ServiceBusConnectionStringProperties.cs @@ -307,6 +307,17 @@ public static ServiceBusConnectionStringProperties Parse(string connectionString { endpointUri = null; } + else if (string.IsNullOrEmpty(endpointUri.Host) && (CountChar(':', value.AsSpan()) == 1)) + { + // If the host was empty after parsing and the value has a single port/scheme separator, + // then the parsing likely failed to recognize the host due to the lack of a scheme. Add + // an artificial scheme and try to parse again. + + if (!Uri.TryCreate($"{ServiceBusEndpointSchemeName}://{value}", UriKind.Absolute, out endpointUri)) + { + endpointUri = null; + } + } var endpointBuilder = endpointUri switch { @@ -371,14 +382,32 @@ public static ServiceBusConnectionStringProperties Parse(string connectionString lastPosition = currentPosition; } - // Enforce that the development emulator can only be used for local development. + return parsedValues; + } - if ((parsedValues.UseDevelopmentEmulator) && (!parsedValues.Endpoint.IsLoopback)) + /// + /// Counts the number of times a character occurs in a given span. + /// + /// + /// The span to evaluate. + /// The character to count. + /// + /// The number of times the occurs in . + /// + private static int CountChar(char value, + ReadOnlySpan span) + { + var count = 0; + + foreach (var character in span) { - throw new ArgumentException("The Service Bus emulator is only available locally. The endpoint must reference to the local host.", connectionString); + if (character == value) + { + ++count; + } } - return parsedValues; + return count; } } } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Primitives/ServiceBusConnectionStringPropertiesTests.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Primitives/ServiceBusConnectionStringPropertiesTests.cs index ed3c3b410be2b..2674b3eb8bfaf 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Primitives/ServiceBusConnectionStringPropertiesTests.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Primitives/ServiceBusConnectionStringPropertiesTests.cs @@ -363,7 +363,7 @@ public void ParseDoesAcceptsHostNamesAndUrisForTheEndpoint(string endpointValue) /// /// [Test] - [TestCase("test.endpoint.com:443")] + [TestCase("test-endpoint|com:443")] [TestCase("notvalid=[broke]")] public void ParseDoesNotAllowAnInvalidEndpointFormat(string endpointValue) { @@ -394,6 +394,7 @@ public void ParseConsidersMissingValuesAsMalformed(string connectionString) /// method. /// /// + [Test] public void ParseDetectsDevelopmentEmulatorUse() { var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true"; @@ -408,6 +409,7 @@ public void ParseDetectsDevelopmentEmulatorUse() /// method. /// /// + [Test] public void ParseRespectsDevelopmentEmulatorValue() { var connectionString = "Endpoint=localhost:1234;SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=false"; @@ -417,6 +419,57 @@ public void ParseRespectsDevelopmentEmulatorValue() Assert.That(parsed.UseDevelopmentEmulator, Is.False, "The development emulator flag should have been unset."); } + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + [TestCase("localhost")] + [TestCase("localhost:9084")] + [TestCase("127.0.0.1")] + [TestCase("local.docker.com")] + [TestCase("local.docker.com:8080")] + [TestCase("www.fake.com")] + [TestCase("www.fake.com:443")] + public void ParseRespectsTheEndpointForDevelopmentEmulatorValue(string host) + { + var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true"; + var endpoint = new Uri(string.Concat(GetServiceBusEndpointScheme(), host)); + var parsed = ServiceBusConnectionStringProperties.Parse(connectionString); + + Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match."); + Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match."); + Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set."); + } + + /// + /// Verifies functionality of the + /// method. + /// + /// + [Test] + [TestCase("sb://localhost")] + [TestCase("http://localhost:9084")] + [TestCase("sb://local.docker.com")] + [TestCase("amqps://local.docker.com:8080")] + [TestCase("sb://www.fake.com")] + [TestCase("amqp://www.fake.com:443")] + public void ParseRespectsTheUrlFormatEndpointForDevelopmentEmulatorValue(string host) + { + var endpoint = new UriBuilder(host) + { + Scheme = GetServiceBusEndpointScheme() + }; + + var connectionString = $"Endpoint={ host };SharedAccessKeyName=[name];SharedAccessKey=[value];UseDevelopmentEmulator=true"; + var parsed = ServiceBusConnectionStringProperties.Parse(connectionString); + + Assert.That(parsed.Endpoint.Host, Is.EqualTo(endpoint.Host), "The endpoint hosts should match."); + Assert.That(parsed.Endpoint.Port, Is.EqualTo(endpoint.Port), "The endpoint ports should match."); + Assert.That(parsed.UseDevelopmentEmulator, Is.True, "The development emulator flag should have been set."); + } + /// /// Verifies functionality of the /// method. @@ -583,32 +636,6 @@ public void ToConnectionStringAllowsSharedAccessSignatureAuthorization() Assert.That(() => properties.ToConnectionString(), Throws.Nothing, "Validation should accept the shared access signature authorization."); } - /// - /// Verifies functionality of the - /// method. - /// - /// - [Test] - [TestCase("localhost", true)] - [TestCase("127.0.0.1", true)] - [TestCase("www.microsoft.com", false)] - [TestCase("fake.servicebus.windows.net", false)] - [TestCase("weirdname:8080", false)] - public void ValidateRequiresLocalEndpointForDevelopmentEmulator(string endpoint, - bool isValid) - { - var fakeConnection = $"Endpoint=sb://{ endpoint };SharedAccessSignature=[not_real];UseDevelopmentEmulator=true"; - - if (isValid) - { - Assert.That(() => ServiceBusConnectionStringProperties.Parse(fakeConnection), Throws.Nothing, "Validation should allow a local endpoint."); - } - else - { - Assert.That(() => ServiceBusConnectionStringProperties.Parse(fakeConnection), Throws.ArgumentException.And.Message.Contains("local"), "Parse should enforce that the endpoint is a local address."); - } - } - /// /// Compares two instances for /// structural equality.