From 631a3b17219f38a43a700d3546f04d5a00492299 Mon Sep 17 00:00:00 2001 From: Clemens Vasters Date: Mon, 26 Aug 2024 10:50:26 -0700 Subject: [PATCH] TcpRemoteForwarder: Parsing the MediaTypeHeaderValue correctly with TryParse and throwing if the parsing fails. (#92) * Fix addresses issue #91 * added HTTP test * config parser adjustment for the HTTP RemoteForward fix --- .../Configuration/Config.cs | 5 +- .../Configuration/RemoteForward.cs | 23 ++++- .../TcpRemoteForwarder.cs | 9 +- .../BridgeTest.cs | 83 +++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Azure.Relay.Bridge/Configuration/Config.cs b/src/Microsoft.Azure.Relay.Bridge/Configuration/Config.cs index 5ff05b1..f84ed5b 100644 --- a/src/Microsoft.Azure.Relay.Bridge/Configuration/Config.cs +++ b/src/Microsoft.Azure.Relay.Bridge/Configuration/Config.cs @@ -913,7 +913,9 @@ namespace Microsoft.Azure.Relay.Bridge.Configuration RemoteForward remoteForward; try { - remoteForward = new RemoteForward { RelayName = rf.Substring(0, firstColon), Http = true }; + // we're not setting the Http flag here but only at the end of the method + // since we don't want a default binding to be created. + remoteForward = new RemoteForward { RelayName = rf.Substring(0, firstColon) }; } catch (ArgumentOutOfRangeException e) { @@ -1028,6 +1030,7 @@ namespace Microsoft.Azure.Relay.Bridge.Configuration } } } + remoteForward.Http = true; } private static void CheckHttpPortName(Config config, string rf, string portName) diff --git a/src/Microsoft.Azure.Relay.Bridge/Configuration/RemoteForward.cs b/src/Microsoft.Azure.Relay.Bridge/Configuration/RemoteForward.cs index 27db6f1..bdda35b 100644 --- a/src/Microsoft.Azure.Relay.Bridge/Configuration/RemoteForward.cs +++ b/src/Microsoft.Azure.Relay.Bridge/Configuration/RemoteForward.cs @@ -148,7 +148,28 @@ namespace Microsoft.Azure.Relay.Bridge.Configuration public bool Http { - get; set; + get + { + if (bindings.Count == 1) + { + return bindings[0].Http; + } + else + { + return false; + } + } + set + { + if (bindings.Count == 0) + { + bindings.Add(new RemoteForwardBinding { Http = value }); + } + else + { + bindings[0].Http = value; + } + } } diff --git a/src/Microsoft.Azure.Relay.Bridge/TcpRemoteForwarder.cs b/src/Microsoft.Azure.Relay.Bridge/TcpRemoteForwarder.cs index 5ba0a5a..e5fd6f3 100644 --- a/src/Microsoft.Azure.Relay.Bridge/TcpRemoteForwarder.cs +++ b/src/Microsoft.Azure.Relay.Bridge/TcpRemoteForwarder.cs @@ -138,7 +138,14 @@ namespace Microsoft.Azure.Relay.Bridge string contentType = context.Request.Headers[HttpRequestHeader.ContentType]; if (!string.IsNullOrEmpty(contentType)) { - requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + if (MediaTypeHeaderValue.TryParse(contentType, out var mediaTypeHeaderValue)) + { + requestMessage.Content.Headers.ContentType = mediaTypeHeaderValue; + } + else + { + throw new InvalidOperationException($"Invalid Content-Type header value: {contentType}"); + } } } diff --git a/test/unit/Microsoft.Azure.Relay.Bridge.Tests/BridgeTest.cs b/test/unit/Microsoft.Azure.Relay.Bridge.Tests/BridgeTest.cs index 25b7a03..2c61d86 100644 --- a/test/unit/Microsoft.Azure.Relay.Bridge.Tests/BridgeTest.cs +++ b/test/unit/Microsoft.Azure.Relay.Bridge.Tests/BridgeTest.cs @@ -6,7 +6,10 @@ namespace Microsoft.Azure.Relay.Bridge.Test using System; using System.IO; using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; using System.Net.Sockets; + using System.Threading.Tasks; using Microsoft.Azure.Relay.Bridge.Configuration; using Microsoft.Azure.Relay.Bridge.Tests; using Xunit; @@ -291,5 +294,85 @@ namespace Microsoft.Azure.Relay.Bridge.Test host.Stop(); } } + + + [Fact] + public void HttpBridge() + { + // set up the bridge first + Config cfg = new Config + { + AzureRelayConnectionString = Utilities.GetConnectionString() + }; + cfg.RemoteForward.Add(new RemoteForward + { + Host = "127.0.97.2", + HostPort = 29877, + PortName = "http", + RelayName = "http", + Http = true + }); + Host host = new Host(cfg); + host.Start(); + + try + { + RelayConnectionStringBuilder csb = new RelayConnectionStringBuilder(Utilities.GetConnectionString()); + var httpEndpoint = new UriBuilder(csb.Endpoint) { Scheme = "https", Port = 443, Path="http" }.Uri; + var httpSasToken = TokenProvider.CreateSharedAccessSignatureTokenProvider(csb.SharedAccessKeyName, csb.SharedAccessKey).GetTokenAsync(httpEndpoint.AbsoluteUri, TimeSpan.FromHours(1)).Result.TokenString; + + using (var l = new HttpListener()) + { + l.Prefixes.Add("http://127.0.97.2:29877/"); + l.Start(); + + var handler = (Task t) => + { + var c = t.Result; + using (var b = new StreamReader(c.Request.InputStream)) + { + var text = b.ReadLine(); + using (var w = new StreamWriter(c.Response.OutputStream)) + { + w.WriteLine(text); + w.Flush(); + } + c.Response.Close(); + } + }; + + var testMessage = "Hello!"; + + + using (var c = new HttpClient()) + { + c.DefaultRequestHeaders.Add("Authorization", httpSasToken); + + // listen for exactly one request + l.GetContextAsync().ContinueWith(handler); + + var r = c.PostAsync(httpEndpoint, new StringContent(testMessage)).GetAwaiter().GetResult(); + Assert.True(r.IsSuccessStatusCode); + var result = r.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + Assert.Equal(testMessage, result.Trim()); + r.Dispose(); + + // listen for exactly one request + l.GetContextAsync().ContinueWith(handler); + + var mtv = MediaTypeHeaderValue.Parse("application/cloudevents+json;charset=utf-8;foo=bar"); + var r2 = c.PostAsync(httpEndpoint, new StringContent(testMessage, mtv)).GetAwaiter().GetResult(); + Assert.True(r2.IsSuccessStatusCode); + var result2 = r2.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + Assert.Equal(testMessage, result2.Trim()); + r2.Dispose(); + } + } + } + finally + { + host.Stop(); + } + } } }