Skip to content

Commit

Permalink
Add support for custom internet proxy credentials with env vars
Browse files Browse the repository at this point in the history
  • Loading branch information
dedale committed Sep 14, 2015
1 parent faaaf6f commit 0b18d17
Show file tree
Hide file tree
Showing 12 changed files with 482 additions and 11 deletions.
7 changes: 7 additions & 0 deletions Paket.sln
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "commands", "commands", "{44
docs\content\commands\update.md = docs\content\commands\update.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paket.Bootstrapper.Tests", "tests\Paket.Bootstrapper.Tests\Paket.Bootstrapper.Tests.csproj", "{7C622582-E281-4EAB-AADA-B5893BB89B45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -95,6 +97,10 @@ Global
{7BAB0AE2-089F-4761-B138-A717AA2F86C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BAB0AE2-089F-4761-B138-A717AA2F86C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BAB0AE2-089F-4761-B138-A717AA2F86C5}.Release|Any CPU.Build.0 = Release|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -104,5 +110,6 @@ Global
{8E6D5255-776D-4B61-85F9-73C37AA1FB9A} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1}
{E789C72A-5CFD-436B-8EF1-61AA2852A89F} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
{4425A246-BD18-4622-86B5-0154F19165E4} = {8E6D5255-776D-4B61-85F9-73C37AA1FB9A}
{7C622582-E281-4EAB-AADA-B5893BB89B45} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source https://nuget.org/api/v2
nuget Newtonsoft.Json
nuget Argu
nuget FSharp.Core
nuget NUnit

github fsharp/FAKE src/app/FakeLib/Globbing/Globbing.fs
github fsprojects/Chessie src/Chessie/ErrorHandling.fs
Expand Down
1 change: 1 addition & 0 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ NUGET
Argu (1.0.0)
FSharp.Core (4.0.0.1)
Newtonsoft.Json (7.0.1)
NUnit (2.6.4)
GITHUB
remote: fsharp/FAKE
specs:
Expand Down
10 changes: 7 additions & 3 deletions src/Paket.Bootstrapper/BootstrapperHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,17 @@ private static void WriteConsole(string message, ConsoleColor consoleColor)

internal static IWebProxy GetDefaultWebProxyFor(String url)
{
IWebProxy result = WebRequest.GetSystemWebProxy();
Uri uri = new Uri(url);
Uri address = result.GetProxy(uri);

IWebProxy result;
if (EnvProxy.TryGetProxyFor(uri, out result) && result.GetProxy(uri) != uri)
return result;

result = WebRequest.GetSystemWebProxy();
Uri address = result.GetProxy(uri);
if (address == uri)
return null;

return new WebProxy(address)
{
Credentials = CredentialCache.DefaultCredentials,
Expand Down
63 changes: 63 additions & 0 deletions src/Paket.Bootstrapper/EnvProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Net;

namespace Paket.Bootstrapper
{
internal class EnvProxy
{
private static readonly EnvProxy instance = new EnvProxy();
private readonly Dictionary<string, IWebProxy> proxies = new Dictionary<string, IWebProxy>(StringComparer.OrdinalIgnoreCase);

private string[] GetBypassList()
{
var noproxy = Environment.GetEnvironmentVariable("NO_PROXY");
if (string.IsNullOrEmpty(noproxy))
return new string[0];
return noproxy.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}

private bool TryGetCredentials(Uri uri, out NetworkCredential credentials)
{
var userPass = uri.UserInfo.Split(new[] { ':' }, 2);
if (userPass.Length == 2 && userPass[0].Length > 0)
{
credentials = new NetworkCredential(Uri.UnescapeDataString(userPass[0]), Uri.UnescapeDataString(userPass[1]));
return true;
}
credentials = null;
return false;
}

private void AddProxy(string scheme, string[] bypassList)
{
var envVarName = string.Format("{0}_PROXY", scheme.ToUpperInvariant());
var envVarValue = Environment.GetEnvironmentVariable(envVarName);
if (envVarValue == null)
return;
Uri envUri;
if (Uri.TryCreate(envVarValue, UriKind.Absolute, out envUri))
{
var proxy = new WebProxy(new Uri(string.Format("{0}://{1}:{2}", scheme, envUri.Host, envUri.Port)));
NetworkCredential credentials;
if (TryGetCredentials(envUri, out credentials))
proxy.Credentials = credentials;
proxy.BypassProxyOnLocal = true;
proxy.BypassList = bypassList;
proxies.Add(scheme, proxy);
}
}

protected EnvProxy()
{
var bypassList = GetBypassList();
AddProxy("http", bypassList);
AddProxy("https", bypassList);
}

public static bool TryGetProxyFor(Uri uri, out IWebProxy proxy)
{
return instance.proxies.TryGetValue(uri.Scheme, out proxy);
}
}
}
1 change: 1 addition & 0 deletions src/Paket.Bootstrapper/Paket.Bootstrapper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<ItemGroup>
<Compile Include="BootstrapperHelper.cs" />
<Compile Include="DownloadArguments.cs" />
<Compile Include="EnvProxy.cs" />
<Compile Include="GitHubDownloadStrategy.cs" />
<Compile Include="IDownloadStrategy.cs" />
<Compile Include="NugetDownloadStrategy.cs" />
Expand Down
3 changes: 3 additions & 0 deletions src/Paket.Bootstrapper/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Paket.Bootstrapper.Tests")]

namespace Paket.Bootstrapper
{
Expand Down
49 changes: 41 additions & 8 deletions src/Paket.Core/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,49 @@ let inline normalizeXml(doc:XmlDocument) =
xmlTextWriter.Flush()
stringWriter.GetStringBuilder().ToString()

let envProxies () =
let bypassList =
let noproxy = Environment.GetEnvironmentVariable("NO_PROXY")
if String.IsNullOrEmpty(noproxy) then [||] else
noproxy.Split([| ',' |], StringSplitOptions.RemoveEmptyEntries)
let getCredentials (uri:Uri) =
let userPass = uri.UserInfo.Split([| ':' |], 2)
if userPass.Length <> 2 || userPass.[0].Length = 0 then None else
let credentials = new NetworkCredential(Uri.UnescapeDataString userPass.[0], Uri.UnescapeDataString userPass.[1])
Some credentials
let getProxy (scheme:string) =
let envVarName = sprintf "%s_PROXY" (scheme.ToUpperInvariant())
let envVarValue = Environment.GetEnvironmentVariable(envVarName)
if envVarValue = null then None else
match Uri.TryCreate(envVarValue, UriKind.Absolute) with
| true, envUri ->
let proxy = new WebProxy(new Uri(sprintf "%s://%s:%d" scheme envUri.Host envUri.Port))
proxy.Credentials <- Option.toObj <| getCredentials envUri
proxy.BypassProxyOnLocal <- true
proxy.BypassList <- bypassList
Some proxy
| _ -> None
let addProxy (map:Map<string, WebProxy>) scheme =
match getProxy scheme with
| Some p -> Map.add scheme p map
| _ -> map
List.fold addProxy Map.empty [ "http"; "https" ]

let getDefaultProxyFor url =
let result = WebRequest.GetSystemWebProxy()
let uri = new Uri(url)
let address = result.GetProxy(uri)

if address = uri then null else
let proxy = new WebProxy(address)
proxy.Credentials <- CredentialCache.DefaultCredentials
proxy.BypassProxyOnLocal <- true
proxy
let getDefault () =
let result = WebRequest.GetSystemWebProxy()
let address = result.GetProxy(uri)

if address = uri then null else
let proxy = new WebProxy(address)
proxy.Credentials <- CredentialCache.DefaultCredentials
proxy.BypassProxyOnLocal <- true
proxy
match envProxies().TryFind uri.Scheme with
| Some p ->
if p.GetProxy(uri) <> uri then p else getDefault()
| None -> getDefault()

let inline createWebClient(url,auth:Auth option) =
let client = new WebClient()
Expand Down
146 changes: 146 additions & 0 deletions tests/Paket.Bootstrapper.Tests/EnvWebProxyShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using NUnit.Framework;
using System;
using System.Net;
using System.Reflection;

namespace Paket.Bootstrapper.Tests
{
[TestFixture]
class EnvWebProxyShould
{
private sealed class FakeEnvProxy : EnvProxy
{
public FakeEnvProxy()
{
var instanceField = typeof(EnvProxy).GetField("instance", BindingFlags.Static | BindingFlags.NonPublic);
instanceField.SetValue(null, this);
}
new public bool TryGetProxyFor(Uri uri, out IWebProxy proxy)
{
return EnvProxy.TryGetProxyFor(uri, out proxy);
}
}
private sealed class DisposableEnvVar : IDisposable
{
private readonly string name;
private readonly string oldValue;
public DisposableEnvVar(string name, string value = null)
{
this.name = name;
oldValue = Environment.GetEnvironmentVariable(name);
Environment.SetEnvironmentVariable(name, value);
}
public void Dispose()
{
Environment.SetEnvironmentVariable(name, oldValue);
}
}

[Test] public void GetNoProxyIfNoneDefined()
{
using (new DisposableEnvVar("http_proxy"))
using (new DisposableEnvVar("https_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsFalse(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
Assert.IsFalse(envProxy.TryGetProxyFor(new Uri("https://github.com"), out proxy));
}
}

[Test] public void GetHttpProxyWithNoPortNoCredentials()
{
using (new DisposableEnvVar("http_proxy", "http://proxy.local"))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
Assert.IsNull(webProxy.Credentials);
}
}

[Test] public void GetHttpProxyWithPortNoCredentials()
{
using (new DisposableEnvVar("http_proxy", "http://proxy.local:8080"))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
Assert.IsNull(webProxy.Credentials);
}
}

[Test] public void GetHttpProxyWithPortAndCredentials()
{
const string password = "p@ssw0rd:";
using (new DisposableEnvVar("http_proxy", string.Format("http://user:{0}@proxy.local:8080", Uri.EscapeDataString(password))))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
var credentials = webProxy.Credentials as NetworkCredential;
Assert.IsNotNull(credentials);
Assert.That(credentials.UserName, Is.EqualTo("user"));
Assert.That(credentials.Password, Is.EqualTo(password));
}
}

[Test] public void GetHttpsProxyWithPortAndCredentials()
{
const string password = "p@ssw0rd:";
using (new DisposableEnvVar("https_proxy", string.Format("https://user:{0}@proxy.local:8080", Uri.EscapeDataString(password))))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("https://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("https://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
var credentials = webProxy.Credentials as NetworkCredential;
Assert.IsNotNull(credentials);
Assert.That(credentials.UserName, Is.EqualTo("user"));
Assert.That(credentials.Password, Is.EqualTo(password));
}
}

[Test] public void GetHttpProxyWithBypassList()
{
using (new DisposableEnvVar("http_proxy", string.Format("http://proxy.local:8080")))
using (new DisposableEnvVar("no_proxy", ".local,127.0.0.1"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(2));
Assert.That(webProxy.BypassList[0], Is.EqualTo(".local"));
Assert.That(webProxy.BypassList[1], Is.EqualTo("127.0.0.1"));
Assert.IsNull(webProxy.Credentials);
}
}
}
}
Loading

0 comments on commit 0b18d17

Please sign in to comment.