Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 36eb70c

Browse files
author
Geoff Kizer
committed
fix IDNA host name handling in proxy case, and add some IDNA tests
1 parent 3b5eb94 commit 36eb70c

File tree

3 files changed

+131
-4
lines changed

3 files changed

+131
-4
lines changed

src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ internal sealed partial class HttpConnection : IDisposable
4242
private static readonly byte[] s_spaceHttp10NewlineAsciiBytes = Encoding.ASCII.GetBytes(" HTTP/1.0\r\n");
4343
private static readonly byte[] s_spaceHttp11NewlineAsciiBytes = Encoding.ASCII.GetBytes(" HTTP/1.1\r\n");
4444
private static readonly byte[] s_hostKeyAndSeparator = Encoding.ASCII.GetBytes(HttpKnownHeaderNames.Host + ": ");
45+
private static readonly byte[] s_httpSchemeAndDelimiter = Encoding.ASCII.GetBytes(Uri.UriSchemeHttp + Uri.SchemeDelimiter);
4546

4647
private readonly HttpConnectionPool _pool;
4748
private readonly HttpConnectionKey _key;
@@ -226,9 +227,16 @@ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, Can
226227
// Write request line
227228
await WriteStringAsync(request.Method.Method, cancellationToken).ConfigureAwait(false);
228229
await WriteByteAsync((byte)' ', cancellationToken).ConfigureAwait(false);
229-
await WriteStringAsync(
230-
_usingProxy ? request.RequestUri.AbsoluteUri : request.RequestUri.PathAndQuery,
231-
cancellationToken).ConfigureAwait(false);
230+
231+
if (_usingProxy)
232+
{
233+
// Proxied requests contain full URL
234+
Debug.Assert(request.RequestUri.Scheme == Uri.UriSchemeHttp);
235+
await WriteBytesAsync(s_httpSchemeAndDelimiter, cancellationToken).ConfigureAwait(false);
236+
await WriteAsciiStringAsync(request.RequestUri.IdnHost, cancellationToken).ConfigureAwait(false);
237+
}
238+
239+
await WriteStringAsync(request.RequestUri.PathAndQuery, cancellationToken).ConfigureAwait(false);
232240

233241
// fall-back to 1.1 for all versions other than 1.0
234242
await WriteBytesAsync(isHttp10 ? s_spaceHttp10NewlineAsciiBytes : s_spaceHttp11NewlineAsciiBytes,
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Net.Test.Common;
7+
using System.Threading.Tasks;
8+
using Xunit;
9+
10+
namespace System.Net.Http.Functional.Tests
11+
{
12+
public abstract class IdnaProtocolTests : HttpClientTestBase
13+
{
14+
private const string s_IdnaHost = "âbçdë.com";
15+
private static Uri s_IdnaUri = new Uri($"http://{s_IdnaHost}/");
16+
private static string s_EncodedHostName = s_IdnaUri.IdnHost;
17+
private static string s_EncodedUri = $"http://{s_EncodedHostName}/";
18+
19+
[Fact]
20+
public async Task InternationalUrl_UsesIdnaEncoding_Success()
21+
{
22+
await LoopbackServer.CreateServerAsync(async (server, url) =>
23+
{
24+
// We don't actually want to do DNS lookup on the IDNA host name in the URL.
25+
// So instead, configure the loopback server as a proxy so we will send to it.
26+
HttpClientHandler handler = CreateHttpClientHandler();
27+
handler.UseProxy = true;
28+
handler.Proxy = new SimpleWebProxy(url);
29+
30+
using (HttpClient client = new HttpClient(handler))
31+
{
32+
Task<HttpResponseMessage> getResponseTask = client.GetAsync(s_IdnaUri);
33+
Task<List<string>> serverTask = LoopbackServer.ReadRequestAndSendResponseAsync(server, LoopbackServer.DefaultHttpResponse);
34+
35+
await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
36+
37+
List<string> requestLines = await serverTask;
38+
39+
// Note since we're using a proxy, host name is included in the request line
40+
Assert.Equal($"GET http://{s_EncodedHostName}/ HTTP/1.1", requestLines[0]);
41+
Assert.Contains($"Host: {s_EncodedHostName}", requestLines);
42+
}
43+
});
44+
}
45+
46+
[ActiveIssue(26355)] // We aren't doing IDNA encoding properly
47+
[Fact]
48+
public async Task InternationalRequestHeaderValues_UsesIdnaEncoding_Success()
49+
{
50+
await LoopbackServer.CreateServerAsync(async (server, url) =>
51+
{
52+
using (HttpClient client = new HttpClient(CreateHttpClientHandler()))
53+
{
54+
var request = new HttpRequestMessage(HttpMethod.Get, url);
55+
request.Headers.Host = s_IdnaHost;
56+
request.Headers.Referrer = s_IdnaUri;
57+
Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
58+
Task<List<string>> serverTask = LoopbackServer.ReadRequestAndSendResponseAsync(server, LoopbackServer.DefaultHttpResponse);
59+
60+
await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
61+
62+
List<string> requestLines = await serverTask;
63+
64+
Assert.Contains($"Host: {s_EncodedHostName}", requestLines);
65+
Assert.Contains($"Referer: {s_EncodedUri}", requestLines);
66+
}
67+
});
68+
}
69+
70+
[ActiveIssue(26355)] // We aren't doing IDNA decoding properly
71+
[Fact]
72+
public async Task InternationalResponseHeaderValues_UsesIdnaDecoding_Success()
73+
{
74+
await LoopbackServer.CreateServerAsync(async (server, url) =>
75+
{
76+
HttpClientHandler handler = CreateHttpClientHandler();
77+
handler.AllowAutoRedirect = false;
78+
79+
using (HttpClient client = new HttpClient(handler))
80+
{
81+
Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
82+
Task<List<string>> serverTask = LoopbackServer.ReadRequestAndSendResponseAsync(server,
83+
$"HTTP/1.1 302 Redirect\r\nLocation: {s_EncodedUri}\r\n\r\n");
84+
85+
await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
86+
87+
HttpResponseMessage response = await getResponseTask;
88+
Console.WriteLine($"Location header={response.Headers.Location}");
89+
Console.WriteLine($"Location header.Host={response.Headers.Location.Host}");
90+
Console.WriteLine($"Location header.IdnHost={response.Headers.Location.IdnHost}");
91+
}
92+
});
93+
}
94+
95+
sealed class SimpleWebProxy : IWebProxy
96+
{
97+
private Uri _proxyUri;
98+
99+
public SimpleWebProxy(Uri proxyUri)
100+
{
101+
_proxyUri = proxyUri;
102+
}
103+
104+
public ICredentials Credentials { get => null; set => throw new NotImplementedException(); }
105+
public Uri GetProxy(Uri destination) => _proxyUri;
106+
public bool IsBypassed(Uri host) => false;
107+
}
108+
}
109+
110+
public sealed class DefaultHandler_IdnaProtocolTests : IdnaProtocolTests
111+
{
112+
}
113+
114+
public sealed class ManagedHandler_IdnaProtocolTests : IdnaProtocolTests
115+
{
116+
protected override bool UseManagedHandler => true;
117+
}
118+
}

src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<Compile Include="HttpContentTest.cs" />
9393
<Compile Include="HttpMessageInvokerTest.cs" />
9494
<Compile Include="HttpMethodTest.cs" />
95+
<Compile Include="IdnaProtocolTests.cs" />
9596
<Compile Include="HttpProtocolTests.cs" />
9697
<Compile Include="HttpRequestMessageTest.cs" />
9798
<Compile Include="HttpResponseMessageTest.cs" />
@@ -141,4 +142,4 @@
141142
<TestCommandLines Include="ulimit -Sn 4096" />
142143
</ItemGroup>
143144
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
144-
</Project>
145+
</Project>

0 commit comments

Comments
 (0)