Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7</TargetFramework>
<TargetFrameworks>net48;net7.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
namespace EasyNetQ.Management.Client.Tests;
#if NETFRAMEWORK
using System;
using System.Reflection;
#endif

namespace EasyNetQ.Management.Client.Tests;

public class ManagementClientConstructorTests
{
Expand All @@ -23,27 +28,60 @@ public void Host_should_have_correct_url_for_ssl()
}

[Theory]
[InlineData("localhost", true)]
[InlineData("127.0.0.1", true)]
[InlineData("::1", false)]
[InlineData("2001:db8:1111::50", false)]
[InlineData("[::1]", true)]
[InlineData("[2001:db8:1111::50]", true)]
[InlineData("[[2001:db8:1111::50]]", false)]
public void Host_url_should_be_legal(string url, bool isValid)
[InlineData("localhost")]
[InlineData("127.0.0.1")]
[InlineData("[::1]")]
[InlineData("[2001:db8:1111::50]")]
public void Host_url_should_be_legal(string url)
{
#pragma warning disable CS0618
var exception = Record.Exception(() => new ManagementClient(url, "user", "password"));
using var client = new ManagementClient(url, "user", "password");
#pragma warning restore CS0618
}

if (isValid)
[Theory]
[InlineData("::1")]
[InlineData("2001:db8:1111::50")]
[InlineData("[[2001:db8:1111::50]]")]
public void Host_url_should_be_illegal(string url)
{
#pragma warning disable CS0618
var exception = Assert.Throws<ArgumentException>(() => new ManagementClient(url, "user", "password"));
#pragma warning restore CS0618

exception.Message.Should().Be("hostUrl is illegal");
}

#if NETFRAMEWORK
[Fact]
public void UriParser_quirks_should_be_fixed()
{
// Enable legacy quirks for https. Must be done before first creation of ManagementClient with https.
string scheme = Uri.UriSchemeHttps;

// https://mikehadlow.blogspot.com/2011/08/how-to-stop-systemuri-un-escaping.html
var getSyntaxMethod =
typeof(UriParser).GetMethod("GetSyntax", BindingFlags.Static | BindingFlags.NonPublic);
if (getSyntaxMethod == null)
{
exception.Should().BeNull();
throw new MissingMethodException("UriParser", "GetSyntax");
}
else
var uriParser = getSyntaxMethod.Invoke(null, new object[] { scheme })!;

var setUpdatableFlagsMethod =
uriParser.GetType().GetMethod("SetUpdatableFlags", BindingFlags.Instance | BindingFlags.NonPublic);
if (setUpdatableFlagsMethod == null)
{
exception.Should().BeOfType<ArgumentException>();
exception.Message.Should().Be("hostUrl is illegal");
throw new MissingMethodException("UriParser", "SetUpdatableFlags");
}
setUpdatableFlagsMethod.Invoke(uriParser, new object[] { 0x2000000 });

string uriWithEscapedDotsAndSlashes = $"{scheme}://localhost/{Uri.EscapeDataString("/.")}";
new Uri(uriWithEscapedDotsAndSlashes).ToString().Should().NotBe(uriWithEscapedDotsAndSlashes);

new ManagementClient(new Uri($"{scheme.ToUpper()}://localhost:15672"), "guest", "guest");

new Uri(uriWithEscapedDotsAndSlashes).ToString().Should().Be(uriWithEscapedDotsAndSlashes);
}
#endif
}
8 changes: 4 additions & 4 deletions Source/EasyNetQ.Management.Client/LegacyEndpointBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public static Uri Build(string hostUrl, int portNumber, bool ssl)

if (ssl)
{
if (hostUrl.Contains("http://")) throw new ArgumentException("hostUrl is illegal");
hostUrl = hostUrl.Contains("https://") ? hostUrl : "https://" + hostUrl;
if (hostUrl.StartsWith("http://")) throw new ArgumentException("hostUrl is illegal");
hostUrl = hostUrl.StartsWith("https://") ? hostUrl : "https://" + hostUrl;
}
else
{
if (hostUrl.Contains("https://")) throw new ArgumentException("hostUrl is illegal");
hostUrl = hostUrl.Contains("http://") ? hostUrl : "http://" + hostUrl;
if (hostUrl.StartsWith("https://")) throw new ArgumentException("hostUrl is illegal");
hostUrl = hostUrl.StartsWith("http://") ? hostUrl : "http://" + hostUrl;
}

if (!UrlRegex.IsMatch(hostUrl)) throw new ArgumentException("hostUrl is illegal");
Expand Down
74 changes: 72 additions & 2 deletions Source/EasyNetQ.Management.Client/ManagementClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#if NET6_0
using HttpHandler = System.Net.Http.SocketsHttpHandler;
#else
using System.Collections.Concurrent;
using System.Reflection;

using HttpHandler = System.Net.Http.HttpClientHandler;
#endif

Expand Down Expand Up @@ -70,6 +73,49 @@ static ManagementClient()
};
}

#if NETSTANDARD2_0
private static bool DisableUriParserLegacyQuirks(string scheme)
{
string uriWithEscapedDotsAndSlashes = $"{scheme}://localhost/{Uri.EscapeDataString("/.")}";
string uriParsed = new Uri(uriWithEscapedDotsAndSlashes).ToString();
if (uriParsed == uriWithEscapedDotsAndSlashes)
{
return false;
}

// https://mikehadlow.blogspot.com/2011/08/how-to-stop-systemuri-un-escaping.html
var getSyntaxMethod =
typeof(UriParser).GetMethod("GetSyntax", BindingFlags.Static | BindingFlags.NonPublic);
if (getSyntaxMethod == null)
{
throw new MissingMethodException("UriParser", "GetSyntax");
}
var uriParser = getSyntaxMethod.Invoke(null, new object[] { scheme })!;

var setUpdatableFlagsMethod =
uriParser.GetType().GetMethod("SetUpdatableFlags", BindingFlags.Instance | BindingFlags.NonPublic);
if (setUpdatableFlagsMethod == null)
{
throw new MissingMethodException("UriParser", "SetUpdatableFlags");
}
setUpdatableFlagsMethod.Invoke(uriParser, new object[] { 0 });

uriParsed = new Uri(uriWithEscapedDotsAndSlashes).ToString();
if (uriParsed != uriWithEscapedDotsAndSlashes)
{
throw new NotImplementedException($"Uri..ctor() can't preserve slashes and dots escaped. Expected={uriWithEscapedDotsAndSlashes}, actual={uriParsed}");
}

return true;
}

private static ConcurrentDictionary<string, bool> s_fixedSchemes = new ConcurrentDictionary<string, bool>();
private static void LeaveDotsAndSlashesEscaped(string scheme)
{
s_fixedSchemes.GetOrAdd(scheme, DisableUriParserLegacyQuirks);
}
#endif

[Obsolete("Please use another constructor")]
public ManagementClient(
string hostUrl,
Expand Down Expand Up @@ -109,6 +155,10 @@ public ManagementClient(
basicAuthHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}")));
httpClient = new HttpClient(httpHandler) { Timeout = timeout ?? DefaultTimeout, BaseAddress = endpoint };
disposeHttpClient = true;

#if NETSTANDARD2_0
LeaveDotsAndSlashesEscaped(Endpoint.Scheme);
#endif
}

public ManagementClient(HttpClient httpClient, string username, string password)
Expand All @@ -123,6 +173,10 @@ public ManagementClient(HttpClient httpClient, string username, string password)
basicAuthHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}")));
configureHttpRequestMessage = null;
disposeHttpClient = false;

#if NETSTANDARD2_0
LeaveDotsAndSlashesEscaped(Endpoint.Scheme);
#endif
}

public Uri Endpoint => httpClient.BaseAddress!;
Expand Down Expand Up @@ -722,7 +776,15 @@ private async Task<T> GetAsync<T>(

response.EnsureExpectedStatusCode(statusCode => statusCode == HttpStatusCode.OK);

return (await response.Content.ReadFromJsonAsync<T>(SerializerOptions, cancellationToken).ConfigureAwait(false))!;
try
{
return (await response.Content.ReadFromJsonAsync<T>(SerializerOptions, cancellationToken).ConfigureAwait(false))!;
}
catch (Exception e) when (e is ArgumentNullException || e is JsonException)
{
string stringContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new JsonException($"Response content doesn't conform to the JSON schema: {stringContent}", e);
}
}

private async Task<TResult> PostAsync<TItem, TResult>(
Expand All @@ -737,7 +799,15 @@ private async Task<TResult> PostAsync<TItem, TResult>(

response.EnsureExpectedStatusCode(statusCode => statusCode is HttpStatusCode.OK or HttpStatusCode.Created or HttpStatusCode.NoContent);

return (await response.Content.ReadFromJsonAsync<TResult>(SerializerOptions, cancellationToken).ConfigureAwait(false))!;
try
{
return (await response.Content.ReadFromJsonAsync<TResult>(SerializerOptions, cancellationToken).ConfigureAwait(false))!;
}
catch (Exception e) when (e is ArgumentNullException || e is JsonException)
{
string stringContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new JsonException($"Response content doesn't conform to the JSON schema: {stringContent}", e);
}
}

private async Task PostAsync<TItem>(
Expand Down