Skip to content

Commit

Permalink
LeaveDotsAndSlashesEscaped for .NET Framework (#314)
Browse files Browse the repository at this point in the history
LeaveDotsAndSlashesEscaped for .NET Framework
  • Loading branch information
inikulshin authored Jan 22, 2024
1 parent 7116ee3 commit 445cdd1
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 22 deletions.
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

0 comments on commit 445cdd1

Please sign in to comment.