Aspnetcore: TestServer not forwarding User-Agent header correctly

Created on 8 Jan 2020  路  5Comments  路  Source: dotnet/aspnetcore

Describe the bug

We're trying to implement unit tests for handling the SameSite mode for cookies which requires probing the User-Agent header. It turns out to be impossible to do via TestServer, because the header gets chopped up into separate values by the HttpClient. It's an array when it gets forwarded to the TestServer here:

https://github.com/dotnet/aspnetcore/blob/2b7e994b8a304700a09617ffc5052f0d943bbcba/src/Hosting/TestHost/src/ClientHandler.cs#L112-L115

lt's impossible to get the value back in a consistent manner, since TestServer behaves differently as an actual server (e.g. IIS); we would like to avoid doing string.Join(" ", request.Headers[HeaderNames.UserAgent]) in our server-side code, only to make the tests work.

To Reproduce

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Net.Http.Headers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestServerHostHEader
{
    [TestClass]
    public class UnitTest
    {

        [TestMethod]
        public async Task ShouldRecieveCorrectUserAgent()
        {
            // Build Proxy TestServer
            var proxyWebHostBuilder = new WebHostBuilder()
                .UseStartup<ProxyStartup>()
                .UseUrls("http://proxy.com:5000");
            var proxyTestServer = new TestServer(proxyWebHostBuilder);

            // Get HttpClient make a request to the proxy
            var httpClient = new HttpClient(proxyTestServer.CreateHandler());
            var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0";
            httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, userAgent);
            var response = await httpClient.GetAsync("http://proxy.com:5000/");
            var responseText = await response.Content.ReadAsStringAsync();

            StringAssert.Contains(responseText, userAgent);
        }

        public class ProxyStartup
        {
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                app.Run(async context =>
                {
                    context.Response.ContentType = "text/plain";
                    await context.Response.WriteAsync(context.Request.Headers[HeaderNames.UserAgent]);
            }
        }
    }
}

Fails with:

StringAssert.Contains failed. String 
'Mozilla/5.0,(Windows NT 10.0; Win64; x64; rv:71.0),Gecko/20100101,Firefox/71.0' does not contain string 
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0'.

Further technical details

> dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.100
 Commit:    cd82f021f4

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.18363
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.1.100\

Host (useful for support):
  Version: 3.1.0
  Commit:  65f04fb6db
Done area-hosting bug

All 5 comments

Looks like something we should fix. We'll take a look.

As a workaround for now, we think using the TestServer.SendAsync API that lets you directly set properties on HttpContext might work.

Thanks for the workaround. It's indeed sufficient for what we want to test!

Here's a SendAsync example:

        [Fact]
        public async Task UserAgentHeaderWorks()
        {
            var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0";
            var builder = new WebHostBuilder().Configure(app => { });
            var server = new TestServer(builder);
            server.BaseAddress = new Uri("https://example.com/");
            var context = await server.SendAsync(c =>
            {
                c.Request.Headers[HeaderNames.UserAgent] = userAgent;
            });

            var actualResult = context.Request.Headers[HeaderNames.UserAgent];
            Assert.Equal(userAgent, actualResult);
        }

@m0sa excellent, glad you're unblocked!

User-Agent defies the normal HTTP rules as it's explicitly space delimited. HttpClient normally deals with this by overriding the separator (only user-agent does this).
https://github.com/dotnet/runtime/blob/aa13fb88d7640f1ccee952976ea45345c97cd7a3/src/libraries/System.Net.Http/src/System/Net/Http/Headers/ProductInfoHeaderParser.cs#L13-L14
https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs#L319-L323
Unfortionately those APIs are internal, we'll have to add our own special case.

Was this page helpful?
0 / 5 - 0 ratings