Skip to content

Commit

Permalink
Add proxy environment variables support for Windows
Browse files Browse the repository at this point in the history
Changed the Windows version of SocketsHttpHandler so that it will honor the same environment
variables similar to the Linux and OSX versions. If the environment variables are not set
then it reverts back to the Windows WinInet/IE settings behavior.

I added several kinds of unit and end-to-end tests to verify the SystemProxyInfo class is
making the correct choice for all the different platforms regarding whether the
HttpEnvironmentProxy or platform proxy (HttpSystemProxy for Windows and MacProxy for OSX)
is used.

Fixes #37187
  • Loading branch information
davidsh committed Apr 27, 2019
1 parent c5bb6ab commit 9ba8879
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 62 deletions.
18 changes: 16 additions & 2 deletions src/Common/tests/System/Net/Http/LoopbackProxyServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ public sealed class LoopbackProxyServer : IDisposable
private const string ProxyAuthenticateNtlmHeader = "Proxy-Authenticate: NTLM\r\n";
private const string ProxyAuthenticateNegotiateHeader = "Proxy-Authenticate: Negotiate\r\n";

private const string ViaHeaderValue = "HTTP/1.1 LoopbackProxyServer";

private readonly Socket _listener;
private readonly Uri _uri;
private readonly AuthenticationSchemes _authSchemes;
private readonly bool _connectionCloseAfter407;
private readonly bool _addViaRequestHeader;
private readonly ManualResetEvent _serverStopped;
private readonly List<ReceivedRequest> _requests;
private int _connections;
Expand All @@ -50,6 +53,7 @@ private LoopbackProxyServer(Options options)
_uri = new Uri($"http://{ep.Address}:{ep.Port}/");
_authSchemes = options.AuthenticationSchemes;
_connectionCloseAfter407 = options.ConnectionCloseAfter407;
_addViaRequestHeader = options.AddViaRequestHeader;
_serverStopped = new ManualResetEvent(false);

_requests = new List<ReceivedRequest>();
Expand Down Expand Up @@ -170,8 +174,15 @@ private async Task<bool> ProcessRequest(StreamReader reader, StreamWriter writer
requestMessage.Headers.Add(header.Key, header.Value);
}
}

// Add 'Via' header.
if (_addViaRequestHeader)
{
requestMessage.Headers.Add("Via", ViaHeaderValue);
}

using (HttpClient outboundClient = new HttpClient())
var handler = new HttpClientHandler() { UseProxy = false };
using (HttpClient outboundClient = new HttpClient(handler))
using (HttpResponseMessage response =
await outboundClient.SendAsync(requestMessage))
{
Expand Down Expand Up @@ -303,7 +314,9 @@ public void Dispose()
_disposed = true;
}
}


public string ViaHeader => ViaHeaderValue;

public class ReceivedRequest
{
public string RequestLine { get; set; }
Expand All @@ -315,6 +328,7 @@ public class Options
{
public AuthenticationSchemes AuthenticationSchemes { get; set; } = AuthenticationSchemes.None;
public bool ConnectionCloseAfter407 { get; set; } = false;
public bool AddViaRequestHeader { get; set; } = false;
}
}
}
3 changes: 3 additions & 0 deletions src/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
<!-- SocketsHttpHandler platform parts -->
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' And '$(TargetGroup)' == 'netcoreapp'">
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.Unix.cs" />
<Compile Include="$(CommonPath)\System\Net\ContextAwareResult.Unix.cs">
<Link>Common\System\Net\ContextAwareResult.Unix.cs</Link>
</Compile>
Expand Down Expand Up @@ -301,6 +302,8 @@
</ItemGroup>
<ItemGroup Condition=" '$(TargetsWindows)' == 'true' And '$(TargetGroup)' == 'netcoreapp'">
<Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.Windows.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpEnvironmentProxy.Windows.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpSystemProxy.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\SChannel\Interop.SecPkgContext_ApplicationProtocol.cs">
<Link>Common\Interop\Windows\SChannel\Interop.SecPkgContext_ApplicationProtocol.cs</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Net.Http
{
internal sealed partial class HttpEnvironmentProxy : IWebProxy
{
public static bool TryCreate(out IWebProxy proxy)
{
// Get environment variables. Protocol specific take precedence over
// general all_*, lower case variable has precedence over upper case.
// Note that curl uses HTTPS_PROXY but not HTTP_PROXY.

Uri httpProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpProxyLC));
if (httpProxy == null && Environment.GetEnvironmentVariable(EnvCGI) == null)
{
httpProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpProxyUC));
}

Uri httpsProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpsProxyLC)) ??
GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpsProxyUC));

if (httpProxy == null || httpsProxy == null)
{
Uri allProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvAllProxyLC)) ??
GetUriFromString(Environment.GetEnvironmentVariable(EnvAllProxyUC));

if (httpProxy == null)
{
httpProxy = allProxy;
}

if (httpsProxy == null)
{
httpsProxy = allProxy;
}
}

// Do not instantiate if nothing is set.
// Caller may pick some other proxy type.
if (httpProxy == null && httpsProxy == null)
{
proxy = null;
return false;
}

string noProxy = Environment.GetEnvironmentVariable(EnvNoProxyLC) ??
Environment.GetEnvironmentVariable(EnvNoProxyUC);
proxy = new HttpEnvironmentProxy(httpProxy, httpsProxy, noProxy);

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Net.Http
{
internal sealed partial class HttpEnvironmentProxy : IWebProxy
{
public static bool TryCreate(out IWebProxy proxy)
{
// Get environment variables. Protocol specific take precedence over
// general all_*. On Windows, environment variables are case insensitive.

Uri httpProxy = null;
if (Environment.GetEnvironmentVariable(EnvCGI) == null)
{
httpProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpProxyUC));
}

Uri httpsProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpsProxyUC));

if (httpProxy == null || httpsProxy == null)
{
Uri allProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvAllProxyUC));

if (httpProxy == null)
{
httpProxy = allProxy;
}

if (httpsProxy == null)
{
httpsProxy = allProxy;
}
}

// Do not instantiate if nothing is set.
// Caller may pick some other proxy type.
if (httpProxy == null && httpsProxy == null)
{
proxy = null;
return false;
}

string noProxy = Environment.GetEnvironmentVariable(EnvNoProxyUC);
proxy = new HttpEnvironmentProxy(httpProxy, httpsProxy, noProxy);

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private static NetworkCredential GetCredentialsFromString(string value)
}
}

internal sealed class HttpEnvironmentProxy : IWebProxy
internal sealed partial class HttpEnvironmentProxy : IWebProxy
{
private const string EnvAllProxyUC = "ALL_PROXY";
private const string EnvAllProxyLC = "all_proxy";
Expand All @@ -105,51 +105,6 @@ internal sealed class HttpEnvironmentProxy : IWebProxy
private string[] _bypass = null;// list of domains not to proxy
private ICredentials _credentials;

public static bool TryCreate(out IWebProxy proxy)
{
// Get environmental variables. Protocol specific take precedence over
// general all_*, lower case variable has precedence over upper case.
// Note that curl uses HTTPS_PROXY but not HTTP_PROXY.

Uri httpProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpProxyLC));
if (httpProxy == null && Environment.GetEnvironmentVariable(EnvCGI) == null)
{
httpProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpProxyUC));
}

Uri httpsProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpsProxyLC)) ??
GetUriFromString(Environment.GetEnvironmentVariable(EnvHttpsProxyUC));

if (httpProxy == null || httpsProxy == null)
{
Uri allProxy = GetUriFromString(Environment.GetEnvironmentVariable(EnvAllProxyLC)) ??
GetUriFromString(Environment.GetEnvironmentVariable(EnvAllProxyUC));

if (httpProxy == null)
{
httpProxy = allProxy;
}
if (httpsProxy == null)
{
httpsProxy = allProxy;
}
}

// Do not instantiate if nothing is set.
// Caller may pick some other proxy type.
if (httpProxy == null && httpsProxy == null)
{
proxy = null;
return false;
}

string noProxy = Environment.GetEnvironmentVariable(EnvNoProxyLC) ??
Environment.GetEnvironmentVariable(EnvNoProxyUC);
proxy = new HttpEnvironmentProxy(httpProxy, httpsProxy, noProxy);

return true;
}

private HttpEnvironmentProxy(Uri httpProxy, Uri httpsProxy, string bypassList)
{
_httpProxyUri = httpProxy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace System.Net.Http
{
internal static class SystemProxyInfo
{
// On Unix we get default proxy configuration from environment variables
// On OSX we get default proxy configuration from either environment variables or the OSX system proxy.
public static IWebProxy ConstructSystemProxy()
{
return HttpEnvironmentProxy.TryCreate(out IWebProxy proxy) ? proxy : new MacProxy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ namespace System.Net.Http
{
internal static class SystemProxyInfo
{
// On Unix we get default proxy configuration from environment variables
// On Unix we get default proxy configuration from environment variables.
public static IWebProxy ConstructSystemProxy()
{
return HttpEnvironmentProxy.TryCreate(out IWebProxy proxy) ? proxy : null;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ namespace System.Net.Http
{
internal static class SystemProxyInfo
{
// On Windows we get default proxy configuration from either environment variables or the Windows system proxy.
public static IWebProxy ConstructSystemProxy()
{
return HttpSystemProxy.TryCreate(out IWebProxy proxy) ? proxy : null;
if (HttpEnvironmentProxy.TryCreate(out IWebProxy proxy))
{
return proxy;
}

HttpSystemProxy.TryCreate(out proxy);

return proxy;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
using System.Text;
using System.Threading.Tasks;

using Microsoft.DotNet.XUnitExtensions;
using Microsoft.DotNet.RemoteExecutor;

using Xunit;
using Xunit.Abstractions;

Expand Down Expand Up @@ -101,6 +104,35 @@ public abstract class HttpClientHandler_Proxy_Test : HttpClientHandlerTestBase
}
}

[OuterLoop("Uses external server")]
[ConditionalFact]
public void Proxy_UseEnvironmentVariableToSetSystemProxy_RequestGoesThruProxy()
{
if (!UseSocketsHttpHandler)
{
throw new SkipTestException("Test needs SocketsHttpHandler");
}

RemoteExecutor.Invoke(async useSocketsHttpHandlerString =>
{
var options = new LoopbackProxyServer.Options { AddViaRequestHeader = true };
using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
{
Environment.SetEnvironmentVariable("http_proxy", proxyServer.Uri.AbsoluteUri.ToString());
using (HttpClient client = CreateHttpClient(useSocketsHttpHandlerString))
using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
string body = await response.Content.ReadAsStringAsync();
Assert.Contains(proxyServer.ViaHeader, body);
}
return RemoteExecutor.SuccessExitCode;
}
}, UseSocketsHttpHandler.ToString()).Dispose();
}

[ActiveIssue(32809)]
[OuterLoop("Uses external server")]
[Theory]
Expand Down
4 changes: 3 additions & 1 deletion src/System.Net.Http/tests/UnitTests/Configurations.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<Project DefaultTargets="Build">
<PropertyGroup>
<BuildConfigurations>
netcoreapp;
netcoreapp-OSX;
netcoreapp-Unix;
netcoreapp-Windows_NT;
</BuildConfigurations>
</PropertyGroup>
</Project>
25 changes: 25 additions & 0 deletions src/System.Net.Http/tests/UnitTests/Fakes/MacProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Net.Http
{
internal sealed class MacProxy : IWebProxy
{
public ICredentials Credentials
{
get => throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented");
set => throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented");
}

public Uri GetProxy(Uri targetUri)
{
throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented");
}

public bool IsBypassed(Uri targetUri)
{
throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented");
}
}
}
Loading

0 comments on commit 9ba8879

Please sign in to comment.