From 322b10f13f43a01f6ad3c59509bbfbcb23f9d1bd Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Mon, 8 Mar 2021 18:17:37 -0800 Subject: [PATCH 1/2] Fix User-Agent usage in SignalR --- .../Http.Connections.Client/src/HttpConnection.cs | 12 ++++++++++-- .../src/Internal/Constants.cs | 7 ++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 6bd1cd88fdee..bb0f786db1df 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -597,8 +597,7 @@ private HttpClient CreateHttpClient() var httpClient = new HttpClient(httpMessageHandler); httpClient.Timeout = HttpClientTimeout; - // Start with the user agent header - httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader); + var userSetUserAgent = false; // Apply any headers configured on the HttpConnectionOptions if (_httpConnectionOptions?.Headers != null) @@ -608,6 +607,7 @@ private HttpClient CreateHttpClient() // Check if the key is User-Agent and remove if empty string then replace if it exists. if (string.Equals(header.Key, Constants.UserAgent, StringComparison.OrdinalIgnoreCase)) { + userSetUserAgent = true; if (string.IsNullOrEmpty(header.Value)) { httpClient.DefaultRequestHeaders.Remove(header.Key); @@ -629,6 +629,14 @@ private HttpClient CreateHttpClient() } } + // Apply default user agent only if user hasn't specified one (empty or not) + // Don't pre-emptively set this, some frameworks (mono) have different user agent format rules, + // so allowing a user to set an empty one avoids throwing on those frameworks. + if (!userSetUserAgent) + { + httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader); + } + httpClient.DefaultRequestHeaders.Remove("X-Requested-With"); // Tell auth middleware to 401 instead of redirecting httpClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest"); diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs index 862772a2a492..ed6687834944 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs @@ -11,11 +11,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { internal static class Constants { - public const string UserAgent = "User-Agent"; + public static readonly string UserAgent = "User-Agent"; public static readonly string UserAgentHeader; static Constants() { + if (OperatingSystem.IsBrowser()) + { + UserAgent = "X-SignalR-User-Agent"; + } + var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttributes() From 993b5b774fe5ae51b99200b707658c6487dfeda8 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Tue, 9 Mar 2021 11:00:17 -0800 Subject: [PATCH 2/2] test? --- .../test/E2ETest/Tests/SignalRClientTest.cs | 16 ++++++++++++++ .../BasicTestApp/SignalRClientComponent.razor | 9 ++++++++ .../test/testassets/TestServer/ChatHub.cs | 21 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/Components/test/E2ETest/Tests/SignalRClientTest.cs b/src/Components/test/E2ETest/Tests/SignalRClientTest.cs index 9265abfc1e1f..82e313f2cd76 100644 --- a/src/Components/test/E2ETest/Tests/SignalRClientTest.cs +++ b/src/Components/test/E2ETest/Tests/SignalRClientTest.cs @@ -66,5 +66,21 @@ public void SignalRClientWorksWithWebSockets() Browser.Equal("SignalR Client: Echo WebSockets", () => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text); } + + [Fact] + public void SignalRClientSendsUserAgent() + { + Browser.Exists(By.Id("hub-url")).SendKeys( + new Uri(_apiServerFixture.RootUri, "/subdir/chathub").AbsoluteUri); + var target = new SelectElement(Browser.Exists(By.Id("transport-type"))); + target.SelectByText("LongPolling"); + Browser.Exists(By.Id("hub-connect")).Click(); + + Browser.Equal("SignalR Client: Echo LongPolling", + () => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text); + + Browser.Exists(By.Id("hub-useragent")).Click(); + Assert.NotNull(Browser.FindElement(By.Id("useragent")).Text); + } } } diff --git a/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor b/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor index a8fae272ab21..dd3a39833169 100644 --- a/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor @@ -13,9 +13,11 @@ +

Connected: @IsConnected
+
User-Agent: @userAgent
    @foreach (var message in messages) @@ -29,6 +31,7 @@ private HttpTransportType transportType; private HubConnection hubConnection; private List messages = new List(); + private string userAgent; protected async Task Connect() { @@ -47,6 +50,12 @@ await hubConnection.SendAsync("SendMessage", "SignalR Client", $"Echo {transportType}"); } + protected async Task GetUserAgentHeader() + { + userAgent = await hubConnection.InvokeAsync("GetHeaderValue", "X-SignalR-User-Agent"); + StateHasChanged(); + } + public bool IsConnected => hubConnection != null && hubConnection.State == HubConnectionState.Connected; diff --git a/src/Components/test/testassets/TestServer/ChatHub.cs b/src/Components/test/testassets/TestServer/ChatHub.cs index 770f2dcaa9f4..eaef0eb927a3 100644 --- a/src/Components/test/testassets/TestServer/ChatHub.cs +++ b/src/Components/test/testassets/TestServer/ChatHub.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; @@ -12,5 +13,25 @@ public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } + + public string GetHeaderValue(string headerName) + { + var context = Context.GetHttpContext(); + + if (context == null) + { + throw new InvalidOperationException("Unable to get HttpContext from request."); + } + + var headers = context.Request.Headers; + + if (headers == null) + { + throw new InvalidOperationException("Unable to get headers from context."); + } + + headers.TryGetValue(headerName, out var val); + return val.ToString(); + } } }