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

Commit 468db63

Browse files
authored
minimal support for ManagedHandler proxy configuration on Windows (#26878)
* initial implementation
1 parent be4cdae commit 468db63

File tree

9 files changed

+317
-30
lines changed

9 files changed

+317
-30
lines changed

src/System.Net.Http/src/System.Net.Http.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
44
<PropertyGroup>
@@ -157,6 +157,7 @@
157157
</ItemGroup>
158158
<ItemGroup Condition=" '$(TargetsWindows)' == 'true' And '$(TargetGroup)' == 'netcoreapp'">
159159
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpProxyConnectionHandler.Windows.cs" />
160+
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpSystemProxy.cs" />
160161
</ItemGroup>
161162
<!-- Common -->
162163
<ItemGroup>

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public NetworkCredential GetCredential(Uri uri, string authType)
3636
uri.Equals(_httpsProxy) ? _httpsCred : null;
3737
}
3838

39-
public static HttpEnvironmentProxyCredentials TryToCreate(Uri httpProxy, Uri httpsProxy)
39+
public static HttpEnvironmentProxyCredentials TryCreate(Uri httpProxy, Uri httpsProxy)
4040
{
4141
NetworkCredential httpCred = null;
4242
NetworkCredential httpsCred = null;
@@ -92,7 +92,7 @@ internal sealed class HttpEnvironmentProxy : IWebProxy
9292
private string[] _bypass = null;// list of domains not to proxy
9393
private ICredentials _credentials;
9494

95-
public static HttpEnvironmentProxy TryToCreate()
95+
public static bool TryCreate(out IWebProxy proxy)
9696
{
9797
// Get environmental variables. Protocol specific take precedence over
9898
// general all_*, lower case variable has precedence over upper case.
@@ -122,18 +122,20 @@ public static HttpEnvironmentProxy TryToCreate()
122122
// Caller may pick some other proxy type.
123123
if (httpProxy == null && httpsProxy == null)
124124
{
125-
return null;
125+
proxy = null;
126+
return false;
126127
}
127128

128-
return new HttpEnvironmentProxy(httpProxy, httpsProxy, Environment.GetEnvironmentVariable(EnvNoProxyLC));
129+
proxy = new HttpEnvironmentProxy(httpProxy, httpsProxy, Environment.GetEnvironmentVariable(EnvNoProxyLC));
130+
return true;
129131
}
130132

131133
private HttpEnvironmentProxy(Uri httpProxy, Uri httpsProxy, string bypassList)
132134
{
133135
_httpProxyUri = httpProxy;
134136
_httpsProxyUri = httpsProxy;
135137

136-
_credentials = HttpEnvironmentProxyCredentials.TryToCreate(httpProxy, httpsProxy);
138+
_credentials = HttpEnvironmentProxyCredentials.TryCreate(httpProxy, httpsProxy);
137139

138140
if (!string.IsNullOrWhiteSpace(bypassList))
139141
{
@@ -172,17 +174,12 @@ private static Uri GetUriFromString(string value)
172174
value = "http://" + value;
173175
}
174176

175-
try
177+
if (Uri.TryCreate(value, UriKind.Absolute, out Uri uri) &&
178+
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
176179
{
177-
Uri uri = new Uri(value);
178-
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
179-
{
180-
// We only support http and https for now.
181-
return uri;
182-
}
180+
// We only support http and https for now.
181+
return uri;
183182
}
184-
catch { };
185-
186183
return null;
187184
}
188185

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ internal sealed partial class HttpProxyConnectionHandler : HttpMessageHandler
99
// On Unix we get default proxy configuration from environment variables
1010
private static IWebProxy ConstructSystemProxy()
1111
{
12-
return HttpEnvironmentProxy.TryToCreate();
12+
return HttpEnvironmentProxy.TryCreate(out IWebProxy proxy) ? proxy : null;
1313
}
1414
}
1515
}

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.Windows.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ internal sealed partial class HttpProxyConnectionHandler : HttpMessageHandler
88
{
99
private static IWebProxy ConstructSystemProxy()
1010
{
11-
// Windows normally does not use environment but it has system
12-
// configuration. That has not been implemented yet.
13-
// TODO #23150: windows portion
14-
return null;
11+
return HttpSystemProxy.TryCreate(out IWebProxy proxy) ? proxy : null;
1512
}
1613
}
1714
}

src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpProxyConnectionHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ protected override void Dispose(bool disposing)
152152
{
153153
_disposed = true;
154154
_connectionPools.Dispose();
155+
if (_proxy is IDisposable obj)
156+
{
157+
obj.Dispose();
158+
}
155159
}
156160

157161
base.Dispose(disposing);
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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;
6+
using System.Net.Http;
7+
using System.Net;
8+
using System.Collections.Generic;
9+
using System.Runtime.InteropServices;
10+
11+
using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle;
12+
13+
namespace System.Net.Http
14+
{
15+
internal sealed class HttpSystemProxy : IWebProxy, IDisposable
16+
{
17+
private readonly Uri _proxyUri; // URI of the system proxy if set
18+
private string[] _bypass; // list of domains not to proxy
19+
private bool _bypassLocal = false; // we should bypass domain considered local
20+
private ICredentials _credentials;
21+
private readonly WinInetProxyHelper _proxyHelper;
22+
private SafeWinHttpHandle _sessionHandle;
23+
private bool _disposed;
24+
25+
public static bool TryCreate(out IWebProxy proxy)
26+
{
27+
// This will get basic proxy setting from system using existing
28+
// WinInetProxyHelper functions. If no proxy is enabled, it will return null.
29+
SafeWinHttpHandle sessionHandle = null;
30+
proxy = null;
31+
32+
WinInetProxyHelper proxyHelper = new WinInetProxyHelper();
33+
if (!proxyHelper.ManualSettingsOnly && !proxyHelper.AutoSettingsUsed)
34+
{
35+
return false;
36+
}
37+
if (proxyHelper.AutoSettingsUsed)
38+
{
39+
sessionHandle = Interop.WinHttp.WinHttpOpen(
40+
IntPtr.Zero,
41+
Interop.WinHttp.WINHTTP_ACCESS_TYPE_NO_PROXY,
42+
Interop.WinHttp.WINHTTP_NO_PROXY_NAME,
43+
Interop.WinHttp.WINHTTP_NO_PROXY_BYPASS,
44+
(int)Interop.WinHttp.WINHTTP_FLAG_ASYNC);
45+
if (sessionHandle.IsInvalid)
46+
{
47+
// Proxy failures are currently ignored by managed handler.
48+
return false;
49+
}
50+
}
51+
proxy = new HttpSystemProxy(proxyHelper, sessionHandle);
52+
return true;
53+
}
54+
55+
private HttpSystemProxy(WinInetProxyHelper proxyHelper, SafeWinHttpHandle sessionHandle)
56+
{
57+
_proxyHelper = proxyHelper;
58+
_sessionHandle = sessionHandle;
59+
60+
if (proxyHelper.ManualSettingsOnly)
61+
{
62+
_proxyUri = GetUriFromString(proxyHelper.Proxy);
63+
64+
if (!string.IsNullOrWhiteSpace(proxyHelper.ProxyBypass))
65+
{
66+
// Process bypass list for manual setting.
67+
string[] list = proxyHelper.ProxyBypass.Split(';');
68+
List<string> tmpList = new List<string>();
69+
70+
foreach (string value in list)
71+
{
72+
string tmp = value.Trim();
73+
if (tmp == "<local>")
74+
{
75+
_bypassLocal = true;
76+
continue;
77+
}
78+
if (tmp.Length > 0)
79+
{
80+
tmpList.Add(tmp);
81+
}
82+
}
83+
if (tmpList.Count > 0)
84+
{
85+
_bypass = tmpList.ToArray();
86+
}
87+
}
88+
}
89+
}
90+
91+
public void Dispose()
92+
{
93+
if (!_disposed)
94+
{
95+
_disposed = true;
96+
97+
if (_sessionHandle != null && !_sessionHandle.IsInvalid)
98+
{
99+
SafeWinHttpHandle.DisposeAndClearHandle(ref _sessionHandle);
100+
}
101+
}
102+
}
103+
104+
/// <summary>
105+
/// This function will evaluate given string and it will try to convert
106+
/// it to Uri object. The string could contain URI fragment, IP address and port
107+
/// tuple or just IP address or name. It will return null if parsing fails.
108+
/// </summary>
109+
private static Uri GetUriFromString(string value)
110+
{
111+
if (string.IsNullOrEmpty(value))
112+
{
113+
return null;
114+
}
115+
116+
if (!value.Contains("://"))
117+
{
118+
value = "http://" + value;
119+
}
120+
121+
if (Uri.TryCreate(value, UriKind.Absolute, out Uri uri) &&
122+
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
123+
{
124+
// We only support http and https for now.
125+
return uri;
126+
}
127+
return null;
128+
}
129+
130+
/// <summary>
131+
/// Gets the proxy URI. (IWebProxy interface)
132+
/// </summary>
133+
public Uri GetProxy(Uri uri)
134+
{
135+
if (_proxyHelper.ManualSettingsOnly)
136+
{
137+
return _proxyUri;
138+
}
139+
var proxyInfo = new Interop.WinHttp.WINHTTP_PROXY_INFO();
140+
try
141+
{
142+
if (_proxyHelper.GetProxyForUrl(_sessionHandle, uri, out proxyInfo))
143+
{
144+
return GetUriFromString(Marshal.PtrToStringUni(proxyInfo.Proxy));
145+
}
146+
}
147+
finally
148+
{
149+
Marshal.FreeHGlobal(proxyInfo.Proxy);
150+
Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
151+
}
152+
return null;
153+
}
154+
155+
/// <summary>
156+
/// Checks if URI is subject to proxy or not.
157+
/// </summary>
158+
public bool IsBypassed(Uri uri)
159+
{
160+
if (_proxyHelper.ManualSettingsOnly)
161+
{
162+
if (_bypassLocal)
163+
{
164+
// TODO #23150: implement bypass match.
165+
}
166+
return false;
167+
}
168+
else if (_proxyHelper.AutoSettingsUsed)
169+
{
170+
// Always return false for now to avoid query to WinHtttp.
171+
// If URI should be bypassed GetProxy() will return null;
172+
return false;
173+
}
174+
return true;
175+
}
176+
177+
public ICredentials Credentials
178+
{
179+
get
180+
{
181+
return _credentials;
182+
}
183+
set
184+
{
185+
_credentials = value;
186+
}
187+
}
188+
}
189+
}

src/System.Net.Http/tests/UnitTests/HttpEnvironmentProxyTest.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ public void HttpProxy_EnvironmentProxy_Loaded()
4545
Uri u;
4646

4747
// It should not return object if there are no variables set.
48-
Assert.Null(HttpEnvironmentProxy.TryToCreate());
48+
Assert.False(HttpEnvironmentProxy.TryCreate(out p));
4949

5050
Environment.SetEnvironmentVariable("all_proxy", "http://1.1.1.1:3000");
51-
p = HttpEnvironmentProxy.TryToCreate();
51+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
5252
Assert.NotNull(p);
5353
Assert.Null(p.Credentials);
5454

@@ -58,7 +58,7 @@ public void HttpProxy_EnvironmentProxy_Loaded()
5858
Assert.True(u != null && u.Host == "1.1.1.1");
5959

6060
Environment.SetEnvironmentVariable("http_proxy", "http://1.1.1.2:3001");
61-
p = HttpEnvironmentProxy.TryToCreate();
61+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
6262
Assert.NotNull(p);
6363

6464
// Protocol specific variables should take precedence over all_
@@ -71,7 +71,7 @@ public void HttpProxy_EnvironmentProxy_Loaded()
7171
// Set https to invalid strings and use only IP & port for http.
7272
Environment.SetEnvironmentVariable("http_proxy", "1.1.1.3:3003");
7373
Environment.SetEnvironmentVariable("https_proxy", "ab!cd");
74-
p = HttpEnvironmentProxy.TryToCreate();
74+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
7575
Assert.NotNull(p);
7676

7777
u = p.GetProxy(fooHttp);
@@ -82,14 +82,14 @@ public void HttpProxy_EnvironmentProxy_Loaded()
8282
// Try valid URI with unsupported protocol. It will be ignored
8383
// to mimic curl behavior.
8484
Environment.SetEnvironmentVariable("https_proxy", "socks5://1.1.1.4:3004");
85-
p = HttpEnvironmentProxy.TryToCreate();
85+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
8686
Assert.NotNull(p);
8787
u = p.GetProxy(fooHttps);
8888
Assert.True(u != null && u.Host == "1.1.1.1" && u.Port == 3000);
8989

9090
// Set https to valid URI but different from http.
9191
Environment.SetEnvironmentVariable("https_proxy", "http://1.1.1.5:3005");
92-
p = HttpEnvironmentProxy.TryToCreate();
92+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
9393
Assert.NotNull(p);
9494

9595
u = p.GetProxy(fooHttp);
@@ -109,19 +109,19 @@ public void HttpProxy_CredentialParsing_Basic()
109109
IWebProxy p;
110110

111111
Environment.SetEnvironmentVariable("all_proxy", "http://foo:bar@1.1.1.1:3000");
112-
p = HttpEnvironmentProxy.TryToCreate();
112+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
113113
Assert.NotNull(p);
114114
Assert.NotNull(p.Credentials);
115115

116116
// Use user only without password.
117117
Environment.SetEnvironmentVariable("all_proxy", "http://foo@1.1.1.1:3000");
118-
p = HttpEnvironmentProxy.TryToCreate();
118+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
119119
Assert.NotNull(p);
120120
Assert.NotNull(p.Credentials);
121121

122122
// Use different user for http and https
123123
Environment.SetEnvironmentVariable("https_proxy", "http://foo1:bar1@1.1.1.1:3000");
124-
p = HttpEnvironmentProxy.TryToCreate();
124+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
125125
Assert.NotNull(p);
126126
Uri u = p.GetProxy(fooHttp);
127127
Assert.NotNull(p.Credentials.GetCredential(u, "Basic"));
@@ -144,7 +144,7 @@ public void HttpProxy_Exceptions_Match()
144144

145145
Environment.SetEnvironmentVariable("no_proxy", ".test.com,, foo.com");
146146
Environment.SetEnvironmentVariable("all_proxy", "http://foo:bar@1.1.1.1:3000");
147-
p = HttpEnvironmentProxy.TryToCreate();
147+
Assert.True(HttpEnvironmentProxy.TryCreate(out p));
148148
Assert.NotNull(p);
149149

150150
Assert.True(p.IsBypassed(fooHttp));

0 commit comments

Comments
 (0)